You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by bb...@apache.org on 2023/12/01 19:58:50 UTC
(hbase) branch branch-2.6 updated: HBASE-25549 Provide a switch that allows avoiding reopening all regions when modifying a table to prevent RIT storms (#5549)
This is an automated email from the ASF dual-hosted git repository.
bbeaudreault pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2.6 by this push:
new edadaec0f8a HBASE-25549 Provide a switch that allows avoiding reopening all regions when modifying a table to prevent RIT storms (#5549)
edadaec0f8a is described below
commit edadaec0f8a6ff63ab6be352403b80acf27887e9
Author: Gourab Taparia <it...@gmail.com>
AuthorDate: Sat Dec 2 01:10:01 2023 +0530
HBASE-25549 Provide a switch that allows avoiding reopening all regions when modifying a table to prevent RIT storms (#5549)
Signed-off-by: Bryan Beaudreault <bb...@apache.org>
Signed-off-by: GeorryHuang <hu...@apache.org>
---
.../java/org/apache/hadoop/hbase/client/Admin.java | 40 +++++++-
.../org/apache/hadoop/hbase/client/AsyncAdmin.java | 15 ++-
.../hadoop/hbase/client/AsyncHBaseAdmin.java | 7 +-
.../org/apache/hadoop/hbase/client/HBaseAdmin.java | 7 +-
.../hadoop/hbase/client/RawAsyncHBaseAdmin.java | 7 +-
.../hbase/shaded/protobuf/RequestConverter.java | 4 +-
.../src/main/protobuf/Master.proto | 1 +
.../src/main/protobuf/MasterProcedure.proto | 1 +
.../org/apache/hadoop/hbase/master/HMaster.java | 15 ++-
.../hadoop/hbase/master/MasterRpcServices.java | 3 +-
.../apache/hadoop/hbase/master/MasterServices.java | 13 ++-
.../master/procedure/ModifyTableProcedure.java | 88 ++++++++++++++--
.../hbase/master/MockNoopMasterServices.java | 6 ++
.../master/procedure/TestModifyTableProcedure.java | 114 +++++++++++++++++++++
hbase-shell/src/main/ruby/hbase/admin.rb | 28 ++++-
hbase-shell/src/main/ruby/hbase_constants.rb | 1 +
hbase-shell/src/main/ruby/shell/commands/alter.rb | 21 ++++
.../hadoop/hbase/thrift2/client/ThriftAdmin.java | 2 +-
18 files changed, 346 insertions(+), 27 deletions(-)
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
index 871c1b82149..8d8a2c90479 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
@@ -1535,7 +1535,21 @@ public interface Admin extends Abortable, Closeable {
throw new IllegalArgumentException("the specified table name '" + tableName
+ "' doesn't match with the HTD one: " + td.getTableName());
}
- modifyTable(td);
+ modifyTable(td, true);
+ }
+
+ /**
+ * Modify an existing table, more IRB friendly version.
+ * @param td modified description of the table
+ * @param reopenRegions By default, 'modifyTable' reopens all regions, potentially causing a
+ * RIT(Region In Transition) storm in large tables. If set to 'false',
+ * regions will remain unaware of the modification until they are
+ * individually reopened. Please note that this may temporarily result in
+ * configuration inconsistencies among regions.
+ * @throws IOException if a remote or network exception occurs
+ */
+ default void modifyTable(TableDescriptor td, boolean reopenRegions) throws IOException {
+ get(modifyTableAsync(td, reopenRegions), getSyncWaitTimeout(), TimeUnit.MILLISECONDS);
}
/**
@@ -1559,7 +1573,7 @@ public interface Admin extends Abortable, Closeable {
* @return the result of the async modify. You can use Future.get(long, TimeUnit) to wait on the
* operation to complete
* @deprecated since 2.0 version and will be removed in 3.0 version. use
- * {@link #modifyTableAsync(TableDescriptor)}
+ * {@link #modifyTableAsync(TableDescriptor, boolean)}
*/
@Deprecated
default Future<Void> modifyTableAsync(TableName tableName, TableDescriptor td)
@@ -1582,7 +1596,27 @@ public interface Admin extends Abortable, Closeable {
* @return the result of the async modify. You can use Future.get(long, TimeUnit) to wait on the
* operation to complete
*/
- Future<Void> modifyTableAsync(TableDescriptor td) throws IOException;
+ default Future<Void> modifyTableAsync(TableDescriptor td) throws IOException {
+ return modifyTableAsync(td, true);
+ }
+
+ /**
+ * Modify an existing table, more IRB (ruby) friendly version. Asynchronous operation. This means
+ * that it may be a while before your schema change is updated across all of the table. You can
+ * use Future.get(long, TimeUnit) to wait on the operation to complete. It may throw
+ * ExecutionException if there was an error while executing the operation or TimeoutException in
+ * case the wait timeout was not long enough to allow the operation to complete.
+ * @param td description of the table
+ * @param reopenRegions By default, 'modifyTableAsync' reopens all regions, potentially causing a
+ * RIT(Region In Transition) storm in large tables. If set to 'false',
+ * regions will remain unaware of the modification until they are
+ * individually reopened. Please note that this may temporarily result in
+ * configuration inconsistencies among regions.
+ * @throws IOException if a remote or network exception occurs
+ * @return the result of the async modify. You can use Future.get(long, TimeUnit) to wait on the
+ * operation to complete
+ */
+ Future<Void> modifyTableAsync(TableDescriptor td, boolean reopenRegions) throws IOException;
/**
* Change the store file tracker of the given table.
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index bde7e950e56..3742fffc256 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -193,7 +193,20 @@ public interface AsyncAdmin {
* Modify an existing table, more IRB friendly version.
* @param desc modified description of the table
*/
- CompletableFuture<Void> modifyTable(TableDescriptor desc);
+ default CompletableFuture<Void> modifyTable(TableDescriptor desc) {
+ return modifyTable(desc, true);
+ }
+
+ /**
+ * Modify an existing table, more IRB friendly version.
+ * @param desc description of the table
+ * @param reopenRegions By default, 'modifyTable' reopens all regions, potentially causing a RIT
+ * (Region In Transition) storm in large tables. If set to 'false', regions
+ * will remain unaware of the modification until they are individually
+ * reopened. Please note that this may temporarily result in configuration
+ * inconsistencies among regions.
+ */
+ CompletableFuture<Void> modifyTable(TableDescriptor desc, boolean reopenRegions);
/**
* Change the store file tracker of the given table.
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
index cf473291319..448cfc9c36e 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
@@ -148,7 +148,12 @@ class AsyncHBaseAdmin implements AsyncAdmin {
@Override
public CompletableFuture<Void> modifyTable(TableDescriptor desc) {
- return wrap(rawAdmin.modifyTable(desc));
+ return modifyTable(desc, true);
+ }
+
+ @Override
+ public CompletableFuture<Void> modifyTable(TableDescriptor desc, boolean reopenRegions) {
+ return wrap(rawAdmin.modifyTable(desc, reopenRegions));
}
@Override
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
index 601f13b4889..0eb1b970cbd 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
@@ -392,7 +392,8 @@ public class HBaseAdmin implements Admin {
}
@Override
- public Future<Void> modifyTableAsync(TableDescriptor td) throws IOException {
+ public Future<Void> modifyTableAsync(TableDescriptor td, boolean reopenRegions)
+ throws IOException {
ModifyTableResponse response = executeCallable(
new MasterCallable<ModifyTableResponse>(getConnection(), getRpcControllerFactory()) {
long nonceGroup = ng.getNonceGroup();
@@ -401,8 +402,8 @@ public class HBaseAdmin implements Admin {
@Override
protected ModifyTableResponse rpcCall() throws Exception {
setPriority(td.getTableName());
- ModifyTableRequest request =
- RequestConverter.buildModifyTableRequest(td.getTableName(), td, nonceGroup, nonce);
+ ModifyTableRequest request = RequestConverter.buildModifyTableRequest(td.getTableName(),
+ td, nonceGroup, nonce, reopenRegions);
return master.modifyTable(getRpcController(), request);
}
});
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
index cf103f401fc..f8fb625eab4 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
@@ -661,9 +661,14 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
@Override
public CompletableFuture<Void> modifyTable(TableDescriptor desc) {
+ return modifyTable(desc, true);
+ }
+
+ public CompletableFuture<Void> modifyTable(TableDescriptor desc, boolean reopenRegions) {
+ // TODO fill the request with reopenRegions
return this.<ModifyTableRequest, ModifyTableResponse> procedureCall(desc.getTableName(),
RequestConverter.buildModifyTableRequest(desc.getTableName(), desc, ng.getNonceGroup(),
- ng.newNonce()),
+ ng.newNonce(), reopenRegions),
(s, c, req, done) -> s.modifyTable(c, req, done), (resp) -> resp.getProcId(),
new ModifyTableProcedureBiConsumer(this, desc.getTableName()));
}
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java
index cd6fd2bb9c4..6ab4817b172 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java
@@ -1306,12 +1306,14 @@ public final class RequestConverter {
* @return a ModifyTableRequest
*/
public static ModifyTableRequest buildModifyTableRequest(final TableName tableName,
- final TableDescriptor tableDesc, final long nonceGroup, final long nonce) {
+ final TableDescriptor tableDesc, final long nonceGroup, final long nonce,
+ boolean reopenRegions) {
ModifyTableRequest.Builder builder = ModifyTableRequest.newBuilder();
builder.setTableName(ProtobufUtil.toProtoTableName(tableName));
builder.setTableSchema(ProtobufUtil.toTableSchema(tableDesc));
builder.setNonceGroup(nonceGroup);
builder.setNonce(nonce);
+ builder.setReopenRegions(reopenRegions);
return builder.build();
}
diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto
index ce736cd799e..a70587d34c3 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Master.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto
@@ -203,6 +203,7 @@ message ModifyTableRequest {
required TableSchema table_schema = 2;
optional uint64 nonce_group = 3 [default = 0];
optional uint64 nonce = 4 [default = 0];
+ optional bool reopen_regions = 5 [default = true];
}
message ModifyTableResponse {
diff --git a/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto b/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto
index b81507269ae..856cf668f18 100644
--- a/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto
@@ -82,6 +82,7 @@ message ModifyTableStateData {
required TableSchema modified_table_schema = 3;
required bool delete_column_family_in_modify = 4;
optional bool should_check_descriptor = 5;
+ optional bool reopen_regions = 6;
}
enum TruncateTableState {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index c92b3559a90..92c65da1600 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -1130,7 +1130,7 @@ public class HMaster extends HRegionServer implements MasterServices {
procedureExecutor.submitProcedure(new ModifyTableProcedure(
procedureExecutor.getEnvironment(), TableDescriptorBuilder.newBuilder(metaDesc)
.setRegionReplication(replicasNumInConf).build(),
- null, metaDesc, false));
+ null, metaDesc, false, true));
}
}
}
@@ -2720,6 +2720,13 @@ public class HMaster extends HRegionServer implements MasterServices {
private long modifyTable(final TableName tableName,
final TableDescriptorGetter newDescriptorGetter, final long nonceGroup, final long nonce,
final boolean shouldCheckDescriptor) throws IOException {
+ return modifyTable(tableName, newDescriptorGetter, nonceGroup, nonce, shouldCheckDescriptor,
+ true);
+ }
+
+ private long modifyTable(final TableName tableName,
+ final TableDescriptorGetter newDescriptorGetter, final long nonceGroup, final long nonce,
+ final boolean shouldCheckDescriptor, final boolean reopenRegions) throws IOException {
return MasterProcedureUtil
.submitProcedure(new MasterProcedureUtil.NonceProcedureRunnable(this, nonceGroup, nonce) {
@Override
@@ -2738,7 +2745,7 @@ public class HMaster extends HRegionServer implements MasterServices {
// checks. This will block only the beginning of the procedure. See HBASE-19953.
ProcedurePrepareLatch latch = ProcedurePrepareLatch.createBlockingLatch();
submitProcedure(new ModifyTableProcedure(procedureExecutor.getEnvironment(),
- newDescriptor, latch, oldDescriptor, shouldCheckDescriptor));
+ newDescriptor, latch, oldDescriptor, shouldCheckDescriptor, reopenRegions));
latch.await();
getMaster().getMasterCoprocessorHost().postModifyTable(tableName, oldDescriptor,
@@ -2755,14 +2762,14 @@ public class HMaster extends HRegionServer implements MasterServices {
@Override
public long modifyTable(final TableName tableName, final TableDescriptor newDescriptor,
- final long nonceGroup, final long nonce) throws IOException {
+ final long nonceGroup, final long nonce, final boolean reopenRegions) throws IOException {
checkInitialized();
return modifyTable(tableName, new TableDescriptorGetter() {
@Override
public TableDescriptor get() throws IOException {
return newDescriptor;
}
- }, nonceGroup, nonce, false);
+ }, nonceGroup, nonce, false, reopenRegions);
}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
index 2924ebef000..86c16f80a25 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
@@ -1484,7 +1484,8 @@ public class MasterRpcServices extends RSRpcServices
throws ServiceException {
try {
long procId = master.modifyTable(ProtobufUtil.toTableName(req.getTableName()),
- ProtobufUtil.toTableDescriptor(req.getTableSchema()), req.getNonceGroup(), req.getNonce());
+ ProtobufUtil.toTableDescriptor(req.getTableSchema()), req.getNonceGroup(), req.getNonce(),
+ req.getReopenRegions());
return ModifyTableResponse.newBuilder().setProcId(procId).build();
} catch (IOException ioe) {
throw new ServiceException(ioe);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
index 35044fb761a..a288bd351c3 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
@@ -152,8 +152,19 @@ public interface MasterServices extends Server {
* @param tableName The table name
* @param descriptor The updated table descriptor
*/
+ default long modifyTable(final TableName tableName, final TableDescriptor descriptor,
+ final long nonceGroup, final long nonce) throws IOException {
+ return modifyTable(tableName, descriptor, nonceGroup, nonce, true);
+ }
+
+ /**
+ * Modify the descriptor of an existing table
+ * @param tableName The table name
+ * @param descriptor The updated table descriptor
+ * @param reopenRegions Whether to reopen regions after modifying the table descriptor
+ */
long modifyTable(final TableName tableName, final TableDescriptor descriptor,
- final long nonceGroup, final long nonce) throws IOException;
+ final long nonceGroup, final long nonce, final boolean reopenRegions) throws IOException;
/**
* Modify the store file tracker of an existing table
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java
index f85fb493fdf..6b86b4fbf02 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java
@@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.master.procedure;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -33,11 +34,13 @@ import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionReplicaUtil;
import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.master.zksyncer.MetaLocationSyncer;
import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerValidationUtils;
import org.apache.hadoop.hbase.replication.ReplicationException;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
import org.apache.yetus.audience.InterfaceAudience;
@@ -56,6 +59,7 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
private TableDescriptor modifiedTableDescriptor;
private boolean deleteColumnFamilyInModify;
private boolean shouldCheckDescriptor;
+ private boolean reopenRegions;
/**
* List of column families that cannot be deleted from the hbase:meta table. They are critical to
* cluster operation. This is a bit of an odd place to keep this list but then this is the tooling
@@ -77,14 +81,15 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
public ModifyTableProcedure(final MasterProcedureEnv env, final TableDescriptor htd,
final ProcedurePrepareLatch latch) throws HBaseIOException {
- this(env, htd, latch, null, false);
+ this(env, htd, latch, null, false, true);
}
public ModifyTableProcedure(final MasterProcedureEnv env,
final TableDescriptor newTableDescriptor, final ProcedurePrepareLatch latch,
- final TableDescriptor oldTableDescriptor, final boolean shouldCheckDescriptor)
- throws HBaseIOException {
+ final TableDescriptor oldTableDescriptor, final boolean shouldCheckDescriptor,
+ final boolean reopenRegions) throws HBaseIOException {
super(env, latch);
+ this.reopenRegions = reopenRegions;
initialize(oldTableDescriptor, shouldCheckDescriptor);
this.modifiedTableDescriptor = newTableDescriptor;
preflightChecks(env, null/* No table checks; if changing peers, table can be online */);
@@ -104,6 +109,60 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
}
}
}
+ if (!reopenRegions) {
+ if (this.unmodifiedTableDescriptor == null) {
+ throw new HBaseIOException(
+ "unmodifiedTableDescriptor cannot be null when this table modification won't reopen regions");
+ }
+ if (
+ !this.unmodifiedTableDescriptor.getTableName()
+ .equals(this.modifiedTableDescriptor.getTableName())
+ ) {
+ throw new HBaseIOException(
+ "Cannot change the table name when this modification won't " + "reopen regions.");
+ }
+ if (
+ this.unmodifiedTableDescriptor.getColumnFamilyCount()
+ != this.modifiedTableDescriptor.getColumnFamilyCount()
+ ) {
+ throw new HBaseIOException(
+ "Cannot add or remove column families when this modification " + "won't reopen regions.");
+ }
+ if (
+ this.unmodifiedTableDescriptor.getCoprocessorDescriptors().hashCode()
+ != this.modifiedTableDescriptor.getCoprocessorDescriptors().hashCode()
+ ) {
+ throw new HBaseIOException(
+ "Can not modify Coprocessor when table modification won't reopen regions");
+ }
+ final Set<String> s = new HashSet<>(Arrays.asList(TableDescriptorBuilder.REGION_REPLICATION,
+ TableDescriptorBuilder.REGION_MEMSTORE_REPLICATION, RSGroupInfo.TABLE_DESC_PROP_GROUP));
+ for (String k : s) {
+ if (
+ isTablePropertyModified(this.unmodifiedTableDescriptor, this.modifiedTableDescriptor, k)
+ ) {
+ throw new HBaseIOException(
+ "Can not modify " + k + " of a table when modification won't reopen regions");
+ }
+ }
+ }
+ }
+
+ /**
+ * Comparing the value associated with a given key across two TableDescriptor instances'
+ * properties.
+ * @return True if the table property <code>key</code> is the same in both.
+ **/
+ private boolean isTablePropertyModified(TableDescriptor oldDescriptor,
+ TableDescriptor newDescriptor, String key) {
+ String oldV = oldDescriptor.getValue(key);
+ String newV = newDescriptor.getValue(key);
+ if (oldV == null && newV == null) {
+ return false;
+ } else if (oldV != null && newV != null && oldV.equals(newV)) {
+ return false;
+ }
+ return true;
}
private void initialize(final TableDescriptor unmodifiedTableDescriptor,
@@ -125,7 +184,13 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
break;
case MODIFY_TABLE_PRE_OPERATION:
preModify(env, state);
- setNextState(ModifyTableState.MODIFY_TABLE_CLOSE_EXCESS_REPLICAS);
+ // We cannot allow changes to region replicas when 'reopenRegions==false',
+ // as this mode bypasses the state management required for modifying region replicas.
+ if (reopenRegions) {
+ setNextState(ModifyTableState.MODIFY_TABLE_CLOSE_EXCESS_REPLICAS);
+ } else {
+ setNextState(ModifyTableState.MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR);
+ }
break;
case MODIFY_TABLE_CLOSE_EXCESS_REPLICAS:
if (isTableEnabled(env)) {
@@ -135,7 +200,11 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
break;
case MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR:
updateTableDescriptor(env);
- setNextState(ModifyTableState.MODIFY_TABLE_REMOVE_REPLICA_COLUMN);
+ if (reopenRegions) {
+ setNextState(ModifyTableState.MODIFY_TABLE_REMOVE_REPLICA_COLUMN);
+ } else {
+ setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION);
+ }
break;
case MODIFY_TABLE_REMOVE_REPLICA_COLUMN:
removeReplicaColumnsIfNeeded(env);
@@ -143,7 +212,11 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
break;
case MODIFY_TABLE_POST_OPERATION:
postModify(env, state);
- setNextState(ModifyTableState.MODIFY_TABLE_REOPEN_ALL_REGIONS);
+ if (reopenRegions) {
+ setNextState(ModifyTableState.MODIFY_TABLE_REOPEN_ALL_REGIONS);
+ } else {
+ return Flow.NO_MORE_STATE;
+ }
break;
case MODIFY_TABLE_REOPEN_ALL_REGIONS:
if (isTableEnabled(env)) {
@@ -238,7 +311,7 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
.setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
.setModifiedTableSchema(ProtobufUtil.toTableSchema(modifiedTableDescriptor))
.setDeleteColumnFamilyInModify(deleteColumnFamilyInModify)
- .setShouldCheckDescriptor(shouldCheckDescriptor);
+ .setShouldCheckDescriptor(shouldCheckDescriptor).setReopenRegions(reopenRegions);
if (unmodifiedTableDescriptor != null) {
modifyTableMsg
@@ -260,6 +333,7 @@ public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<Mod
deleteColumnFamilyInModify = modifyTableMsg.getDeleteColumnFamilyInModify();
shouldCheckDescriptor =
modifyTableMsg.hasShouldCheckDescriptor() ? modifyTableMsg.getShouldCheckDescriptor() : false;
+ reopenRegions = modifyTableMsg.hasReopenRegions() ? modifyTableMsg.getReopenRegions() : true;
if (modifyTableMsg.hasUnmodifiedTableSchema()) {
unmodifiedTableDescriptor =
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
index a0951480124..8075c558dee 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
@@ -258,6 +258,12 @@ public class MockNoopMasterServices implements MasterServices {
return -1;
}
+ @Override
+ public long modifyTable(TableName tableName, TableDescriptor descriptor, long nonceGroup,
+ long nonce, boolean reopenRegions) throws IOException {
+ return -1;
+ }
+
@Override
public long enableTable(final TableName tableName, final long nonceGroup, final long nonce)
throws IOException {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestModifyTableProcedure.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestModifyTableProcedure.java
index 81ec43402a4..91ce3866f63 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestModifyTableProcedure.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestModifyTableProcedure.java
@@ -25,20 +25,24 @@ import java.io.IOException;
import org.apache.hadoop.hbase.ConcurrentTableModificationException;
import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseIOException;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.InvalidFamilyOperationException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
+import org.apache.hadoop.hbase.client.CoprocessorDescriptorBuilder;
import org.apache.hadoop.hbase.client.PerClientRandomNonceGenerator;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.master.procedure.MasterProcedureTestingUtility.StepHook;
import org.apache.hadoop.hbase.procedure2.Procedure;
import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
+import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.util.Bytes;
@@ -569,4 +573,114 @@ public class TestModifyTableProcedure extends TestTableDDLProcedureBase {
t2.join();
assertFalse("Expected ConcurrentTableModificationException.", (t1.exception || t2.exception));
}
+
+ @Test
+ public void testModifyWillNotReopenRegions() throws IOException {
+ final boolean reopenRegions = false;
+ final TableName tableName = TableName.valueOf(name.getMethodName());
+ final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
+
+ MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf");
+
+ // Test 1: Modify table without reopening any regions
+ TableDescriptor htd = UTIL.getAdmin().getDescriptor(tableName);
+ TableDescriptor modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd)
+ .setValue("test" + ".hbase.conf", "test.hbase.conf.value").build();
+ long procId1 = ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure(
+ procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions));
+ ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
+ TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(tableName);
+ assertEquals("test.hbase.conf.value", currentHtd.getValue("test.hbase.conf"));
+ // Regions should not aware of any changes.
+ for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) {
+ Assert.assertNull(r.getTableDescriptor().getValue("test.hbase.conf"));
+ }
+ // Force regions to reopen
+ for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) {
+ getMaster().getAssignmentManager().move(r.getRegionInfo());
+ }
+ // After the regions reopen, ensure that the configuration is updated.
+ for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) {
+ assertEquals("test.hbase.conf.value", r.getTableDescriptor().getValue("test.hbase.conf"));
+ }
+
+ // Test 2: Modifying region replication is not allowed
+ htd = UTIL.getAdmin().getDescriptor(tableName);
+ long oldRegionReplication = htd.getRegionReplication();
+ modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd).setRegionReplication(3).build();
+ try {
+ ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure(
+ procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions));
+ Assert.fail(
+ "An exception should have been thrown while modifying region replication properties.");
+ } catch (HBaseIOException e) {
+ assertTrue(e.getMessage().contains("Can not modify"));
+ }
+ currentHtd = UTIL.getAdmin().getDescriptor(tableName);
+ // Nothing changed
+ assertEquals(oldRegionReplication, currentHtd.getRegionReplication());
+
+ // Test 3: Adding CFs is not allowed
+ htd = UTIL.getAdmin().getDescriptor(tableName);
+ modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd)
+ .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder("NewCF".getBytes()).build())
+ .build();
+ try {
+ ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure(
+ procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions));
+ Assert.fail("Should have thrown an exception while modifying CF!");
+ } catch (HBaseIOException e) {
+ assertTrue(e.getMessage().contains("Cannot add or remove column families"));
+ }
+ currentHtd = UTIL.getAdmin().getDescriptor(tableName);
+ Assert.assertNull(currentHtd.getColumnFamily("NewCF".getBytes()));
+
+ // Test 4: Modifying CF property is allowed
+ htd = UTIL.getAdmin().getDescriptor(tableName);
+ modifiedDescriptor =
+ TableDescriptorBuilder
+ .newBuilder(htd).modifyColumnFamily(ColumnFamilyDescriptorBuilder
+ .newBuilder("cf".getBytes()).setCompressionType(Compression.Algorithm.SNAPPY).build())
+ .build();
+ ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure(
+ procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions));
+ for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) {
+ Assert.assertEquals(Compression.Algorithm.NONE,
+ r.getTableDescriptor().getColumnFamily("cf".getBytes()).getCompressionType());
+ }
+ for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) {
+ getMaster().getAssignmentManager().move(r.getRegionInfo());
+ }
+ for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) {
+ Assert.assertEquals(Compression.Algorithm.SNAPPY,
+ r.getTableDescriptor().getColumnFamily("cf".getBytes()).getCompressionType());
+ }
+
+ // Test 5: Modifying coprocessor is not allowed
+ htd = UTIL.getAdmin().getDescriptor(tableName);
+ modifiedDescriptor =
+ TableDescriptorBuilder.newBuilder(htd).setCoprocessor(CoprocessorDescriptorBuilder
+ .newBuilder("any.coprocessor.name").setJarPath("fake/path").build()).build();
+ try {
+ ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure(
+ procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions));
+ Assert.fail("Should have thrown an exception while modifying coprocessor!");
+ } catch (HBaseIOException e) {
+ assertTrue(e.getMessage().contains("Can not modify Coprocessor"));
+ }
+ currentHtd = UTIL.getAdmin().getDescriptor(tableName);
+ assertEquals(0, currentHtd.getCoprocessorDescriptors().size());
+
+ // Test 6: Modifying REGION_REPLICATION is not allowed.
+ htd = UTIL.getAdmin().getDescriptor(tableName);
+ modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd).setRegionReplication(3).build();
+ try {
+ ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure(
+ procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions));
+ Assert.fail("Should have thrown an exception while modifying REGION_REPLICATION!");
+ } catch (HBaseIOException e) {
+ System.out.println(e.getMessage());
+ assertTrue(e.getMessage().contains("Can not modify REGION_REPLICATION"));
+ }
+ }
}
diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb
index 2110e72d155..1f2e684dce9 100644
--- a/hbase-shell/src/main/ruby/hbase/admin.rb
+++ b/hbase-shell/src/main/ruby/hbase/admin.rb
@@ -717,6 +717,7 @@ module Hbase
# Get table descriptor
htd = org.apache.hadoop.hbase.HTableDescriptor.new(@admin.getTableDescriptor(table_name))
hasTableUpdate = false
+ reopen_regions = true
# Process all args
args.each do |arg|
@@ -726,9 +727,18 @@ module Hbase
# Normalize args to support shortcut delete syntax
arg = { METHOD => 'delete', NAME => arg['delete'] } if arg['delete']
+ if arg.key?(REOPEN_REGIONS)
+ if !['true', 'false'].include?(arg[REOPEN_REGIONS].downcase)
+ raise(ArgumentError, "Invalid 'REOPEN_REGIONS' for non-boolean value.")
+ end
+ reopen_regions = JBoolean.valueOf(arg[REOPEN_REGIONS])
+ arg.delete(REOPEN_REGIONS)
+ end
+
# There are 3 possible options.
# 1) Column family spec. Distinguished by having a NAME and no METHOD.
method = arg.delete(METHOD)
+
if method.nil? && arg.key?(NAME)
descriptor = hcd(arg, htd)
column_name = descriptor.getNameAsString
@@ -841,11 +851,23 @@ module Hbase
# Bulk apply all table modifications.
if hasTableUpdate
- @admin.modifyTable(table_name, htd)
+ future = @admin.modifyTableAsync(htd, reopen_regions)
+ if reopen_regions == false
+ puts("WARNING: You are using REOPEN_REGIONS => 'false' to modify a table, which will
+ result in inconsistencies in the configuration of online regions and other risks. If you
+ encounter any issues, use the original 'alter' command to make the modification again!")
+ end
if wait == true
- puts 'Updating all regions with the new schema...'
- alter_status(table_name_str)
+ # If the changes(reopen_regions=false) are not getting applied immediately -
+ # showing the below can be confusing, hence we only show it when reopen_regions is true.
+ if reopen_regions == true
+ puts 'Updating all regions with the new schema...'
+ end
+ future.get
+ if reopen_regions == true
+ alter_status(table_name_str)
+ end
end
end
end
diff --git a/hbase-shell/src/main/ruby/hbase_constants.rb b/hbase-shell/src/main/ruby/hbase_constants.rb
index 76631c91cf9..ee6b8f22f42 100644
--- a/hbase-shell/src/main/ruby/hbase_constants.rb
+++ b/hbase-shell/src/main/ruby/hbase_constants.rb
@@ -99,6 +99,7 @@ module HBaseConstants
VALUE = 'VALUE'.freeze
VERSIONS = org.apache.hadoop.hbase.HConstants::VERSIONS
VISIBILITY = 'VISIBILITY'.freeze
+ REOPEN_REGIONS = 'REOPEN_REGIONS'.freeze
# aliases
ENDKEY = STOPROW
diff --git a/hbase-shell/src/main/ruby/shell/commands/alter.rb b/hbase-shell/src/main/ruby/shell/commands/alter.rb
index 9d95bffbdda..85881fb41b1 100644
--- a/hbase-shell/src/main/ruby/shell/commands/alter.rb
+++ b/hbase-shell/src/main/ruby/shell/commands/alter.rb
@@ -72,6 +72,27 @@ You can also set configuration settings specific to this table or column family:
hbase> alter 't1', CONFIGURATION => {'hbase.hregion.scan.loadColumnFamiliesOnDemand' => 'true'}
hbase> alter 't1', {NAME => 'f2', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}}
+You can also set configuration setting with REOPEN_REGIONS=>'false' to avoid regions RIT, which
+let the modification take effect after regions was reopened (Be careful, the regions of the table
+may be configured inconsistently If regions are not reopened after the modification)
+
+ hbase> alter 't1', REOPEN_REGIONS => 'false', MAX_FILESIZE => '134217728'
+ hbase> alter 't1', REOPEN_REGIONS => 'false', CONFIGURATION => {'hbase.hregion.scan
+ .loadColumnFamiliesOnDemand' => 'true'}
+
+However, be aware that:
+1. Inconsistency Risks: If the regions are not reopened after the modification, the table's regions
+may become inconsistently configured. Ensure that you manually reopen the regions as soon as
+possible to apply the changes consistently across the entire table.
+2. If changes are made to the table without reopening the regions, we currently only allow
+lightweight operations. The following types of changes, which may lead to unknown situations,
+will throw an exception:
+ a. Adding or removing CFs, coprocessors.
+ b. Modifying the table name.
+ c. Changing region replica related configurations such as 'REGION_REPLICATION'
+ and 'REGION_MEMSTORE_REPLICATION'.
+ d. Changing the rsgroup.
+
You can also unset configuration settings specific to this table:
hbase> alter 't1', METHOD => 'table_conf_unset', NAME => 'hbase.hregion.majorcompaction'
diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
index 76deb6fdd6a..94da27933cf 100644
--- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
+++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
@@ -922,7 +922,7 @@ public class ThriftAdmin implements Admin {
}
@Override
- public Future<Void> modifyTableAsync(TableDescriptor td) {
+ public Future<Void> modifyTableAsync(TableDescriptor td, boolean reopenRegions) {
throw new NotImplementedException("modifyTableAsync not supported in ThriftAdmin");
}