Skip to content

HBASE-29881: Change TableName.META_TABLE_NAME from being a global static - #7730

Open
Kota-SH wants to merge 2 commits into
apache:masterfrom
Kota-SH:HBASE-29691-new
Open

Kota-SH wants to merge 2 commits into
apache:masterfrom
Kota-SH:HBASE-29691-new

Conversation

@Kota-SH

@Kota-SH Kota-SH commented Feb 10, 2026 •

Copy link
Copy Markdown
Contributor

HBASE-29881: Change TableName.META_TABLE_NAME from being a global static

Change TableName.META_TABLE_NAME from a global static constant to a dynamically discovered value from ConnectionRegistry.

Please refer to earlier discussion on the PR #7558 for further details.

Key changes:

  • Add ConnectionRegistry.getMetaTableName() method for dynamic discovery
  • Add MetaTableNameStore for persisting meta table name in master region
  • Update TableName to support dynamic meta table name
  • Update HMaster to integrate with MetaTableNameStore
  • Update all client and server code to use dynamic meta table name
  • Add protobuf changes for meta table name in Registry.proto

Refactoring for the test classes to be handled separately.

@anmolnar anmolnar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. What are the build failures?

@kgeisz kgeisz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall. I have some nit comments below. Also, I see PR #7558 is still open. Should that PR be closed?

+ hbi.getMetaEntry().regionServer + " but is multiply assigned to region servers "
+ Joiner.on(", ").join(hbi.getDeployedOn()));
"Region " + descriptiveName + " is listed in " + connection.getMetaTableName()
+ " on region server " + hbi.getMetaEntry().regionServer + " but is multiply assigned"

@kgeisz kgeisz Feb 11, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Do you have any idea what they mean by "multiply assigned"? Like "multi-assigned"?

@Apache9

Apache9 commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

Just came back from vacation...
This is a big change, please give me more time for reviewing...
Thanks.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements HBASE-29691, which changes TableName.META_TABLE_NAME from a global static constant to a dynamically discovered value. The change enables support for cluster-specific meta table names, particularly for read replica clusters and other specialized configurations.

Changes:

  • Adds ConnectionRegistry.getMetaTableName() method for dynamic meta table name discovery
  • Introduces MetaTableNameStore class to persist the meta table name in the Master Local Region
  • Updates Connection, AsyncConnection, and MasterServices interfaces to expose getMetaTableName() method
  • Modifies TableName.isMetaTableName() to support both default "hbase:meta" and variant table names with suffixes
  • Updates all client and server code paths to use the dynamic meta table name instead of the static constant
  • Adds new RPC endpoint GetMetaTableName in Registry.proto for retrieving the meta table name from the master

Reviewed changes

Copilot reviewed 98 out of 98 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
hbase-protocol-shaded/src/main/protobuf/server/Registry.proto Adds GetMetaTableName RPC request/response messages
hbase-server/src/main/java/org/apache/hadoop/hbase/master/MetaTableNameStore.java New class to store/retrieve meta table name from Master Local Region
hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java Integrates MetaTableNameStore and overrides getMetaTableName()
hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java Implements getMetaTableName() to fetch from master via connection
hbase-client/src/main/java/org/apache/hadoop/hbase/client/Connection.java Adds getMetaTableName() and getMetaTable() helper methods
hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncConnection.java Adds getMetaTableName() and getMetaTable() helper methods
hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionRegistry.java Adds getMetaTableName() method for discovering meta table name
hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncConnectionImpl.java Stores and exposes meta table name fetched from registry
hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionFactory.java Fetches meta table name from registry during connection creation
hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java Updates isMetaTableName() to support meta table name variants, deprecates META_TABLE_NAME
hbase-common/src/main/java/org/apache/hadoop/hbase/CellComparatorImpl.java Updates comparator selection to support dynamic meta table names
hbase-server/src/main/java/org/apache/hadoop/hbase/HBaseServerBase.java Provides default getMetaTableName() implementation
hbase-server/src/main/java/org/apache/hadoop/hbase/HBaseRpcServicesBase.java Implements getMetaTableName() RPC handler
Multiple server-side utilities Updates HBaseFsck, RegionMover, MetaTableAccessor, and other utilities to use dynamic meta table name
Multiple test files Updates test infrastructure and mocks to support dynamic meta table name

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1204 to +1208
LOG.info(
"The {} config is {} while the replica count in TableDescriptor is {}"
+ " for hbase:meta, altering...",
HConstants.META_REPLICAS_NUM, replicasNumInConf, metaDesc.getRegionReplication());
HConstants.META_REPLICAS_NUM, replicasNumInConf, metaDesc.getRegionReplication(),
getMetaTableName());

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log format at line 1207 has incorrect parameter count. The format string has 3 placeholders ("The {} config is {} while the replica count in TableDescriptor is {} for hbase:meta, altering...") but 4 arguments are provided: HConstants.META_REPLICAS_NUM, replicasNumInConf, metaDesc.getRegionReplication(), and getMetaTableName(). This will cause a logging error. The fourth parameter getMetaTableName() should be part of the format string template, not an extra argument.

Copilot uses AI. Check for mistakes.
Comment on lines +1688 to +1691
} catch (IOException e) {
LOG.info("Exception loading/storing meta table name from master region");
throw new RuntimeException(e);
}

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message at line 1689 is missing important context. When logging an exception, it should describe what operation failed. The message "Exception loading/storing meta table name from master region" should be logged with the exception using LOG.error() or LOG.warn() with the exception as the second parameter, not LOG.info(). Also, rethrowing as RuntimeException loses the original exception message - consider using new RuntimeException("Exception loading/storing meta table name from master region", e) to preserve the exception chain.

Copilot uses AI. Check for mistakes.
}

static boolean isMetaTable(byte[] tableName) {
return Bytes.startsWith(tableName, TableName.META_TABLE_NAME.getName());

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation uses TableName.META_TABLE_NAME.getName() as a prefix check, but this could match unintended table names. For example, if someone creates a table "hbase:metadata", this check would incorrectly return true because it starts with "hbase:meta". The logic should verify exact match or use a more precise delimiter-based check. Consider checking if the bytes are exactly equal OR if they match the pattern with an underscore separator.

Suggested change
return Bytes.startsWith(tableName, TableName.META_TABLE_NAME.getName());
byte[] metaName = TableName.META_TABLE_NAME.getName();
// Match exactly "hbase:meta" or names starting with "hbase:meta_" (e.g., "hbase:meta_1").
if (Bytes.equals(tableName, metaName)) {
return true;
}
if (tableName == null || tableName.length <= metaName.length) {
return false;
}
if (!Bytes.startsWith(tableName, metaName)) {
return false;
}
// Require an underscore delimiter after "hbase:meta" to avoid matching names like "hbase:metadata".
return tableName[metaName.length] == (byte) '_';

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to be more specific here.

  • Table name equals to hbase:meta, or
  • Table name matches pattern hbase:meta_[a-z0-9]+

As long as we agree on that meta table name will be generated from a hash value.

Comment on lines +146 to +148
private static TableName getMetaTableNameFromConf(Configuration conf) {
// TODO: Support replica-specific meta table names from masterRegion
return TableName.META_TABLE_NAME;

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO comment on line 147 mentions "Support replica-specific meta table names from masterRegion" but this contradicts the PR's design. The PR adds MetaTableNameStore which stores the meta table name in the master region. This method, however, is static and doesn't have access to the master region. The comment and implementation should be aligned - either this method should not exist (and callers should use the dynamic meta table name from the connection/master), or the TODO should explain how static methods will interact with the dynamic meta table name system.

Copilot uses AI. Check for mistakes.
Comment on lines +372 to +375
@Override
public TableName getMetaTableName() {
return toAsyncConnection().getMetaTableName();
}

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling toAsyncConnection().getMetaTableName() on line 374 will throw NotImplementedException because toAsyncConnection() on line 379 throws NotImplementedException. This creates a method that always throws an exception, which is a bug. The getMetaTableName() implementation should either throw NotImplementedException directly or have a proper implementation that doesn't depend on toAsyncConnection().

Copilot uses AI. Check for mistakes.
Comment on lines +3679 to +3689
public TableName getMetaTableName() {
if (asyncClusterConnection != null) {
try {
return asyncClusterConnection.getMetaTableName();
} catch (Exception e) {
LOG.warn("Failed to get meta table name from Master", e);
}
}
// Bootstrap
return super.getMetaTableName();
}

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getMetaTableName() method in HRegionServer could return different values over time, which could cause inconsistencies. During bootstrap (before asyncClusterConnection is set), it returns the default meta table name from the parent class. After the connection is established, it returns the value from the connection. If there's any caching or state that depends on this value, changing it mid-execution could cause issues. Consider caching the value once it's successfully retrieved from the connection to ensure consistency.

Copilot uses AI. Check for mistakes.
@anmolnar

Copy link
Copy Markdown
Contributor

Regarding Copilot's concerns about incorrect meta table name prefix matching: is the user able to create tables in the hbase: namespace?

@anmolnar anmolnar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of review.

Comment on lines +45 to +46
// TODO: For now, hardcode to default. Future: lazy initialization based on config or make it use
// connection

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to work for now with the default name?

@Test
public void testCreateMetaTable() {
conn.getAdmin().createTable(TableDescriptorBuilder.newBuilder(TableName.META_TABLE_NAME)
conn.getAdmin().createTable(TableDescriptorBuilder.newBuilder(conn.getMetaTableName())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that we don't change the default meta table name in unit tests in this patch for now. Rather we'll do them at once if needed. Why is this change?

@Test
public void testClearCacheTableName() {
conn.getLocator().clearCache(TableName.META_TABLE_NAME);
conn.getLocator().clearCache(conn.getMetaTableName());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Are these tests not passing without the modification?

}

static boolean isMetaTable(byte[] tableName) {
return Bytes.startsWith(tableName, TableName.META_TABLE_NAME.getName());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to be more specific here.

  • Table name equals to hbase:meta, or
  • Table name matches pattern hbase:meta_[a-z0-9]+

As long as we agree on that meta table name will be generated from a hash value.

Comment on lines 97 to 105
public static boolean isMetaTableName(final TableName tn) {
return tn.equals(TableName.META_TABLE_NAME);
if (
tn == null || !tn.getNamespaceAsString().equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR)
) {
return false;
}
String qualifier = tn.getQualifierAsString();
return qualifier.equals("meta") || qualifier.startsWith("meta_");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this validation at a single place potentially here in this class?

@Override
public TableName getTableName() {
// Always returns default "hbase:meta" for now
// Future: will support replica-specific names (e.g., "hbase:meta_replica1")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this will be the point where we'll populate the meta table name from config.

@anmolnar

Copy link
Copy Markdown
Contributor

@kgeisz @Apache9 Please take another look at this patch. It's in a good shape. The test error is a flaky. Thanks!

@anmolnar anmolnar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. A few of my previous questions haven't been answered yet.

Comment on lines +755 to +758
// For now, it is hbase:meta because we don't support custom meta table name.
// After adding support for custom meta table names, we can calculate this from conf and use it
// downstream to persist it in Master Region.
return getDefaultMetaTableName();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment still valid?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also wondering this

@Kota-SH
Kota-SH force-pushed the HBASE-29691-new branch 2 times, most recently from 4b14bc6 to 76fd869 Compare October 8, 2026 18:48

@kgeisz kgeisz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments after doing another review

Comment on lines +799 to +805
static boolean isMetaTable(byte[] tableName) {
try {
return TableName.isMetaTableName(TableName.valueOf(tableName));
} catch (IllegalArgumentException e) {
return false;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Claude:

Replace the "TableName.valueOf() + regex" approach with a fast-path byte comparison against the default hbase:meta bytes, falling back to the general check only for non-matching names.

Suggested change
static boolean isMetaTable(byte[] tableName) {
try {
return TableName.isMetaTableName(TableName.valueOf(tableName));
} catch (IllegalArgumentException e) {
return false;
}
}
static boolean isMetaTable(byte[] tableName) {
if (Bytes.equals(tableName, TableName.META_TABLE_NAME.toBytes())) {
return true;
}
try {
return TableName.isMetaTableName(TableName.valueOf(tableName));
} catch (IllegalArgumentException e) {
return false;
}
}

future.completeExceptionally(e);
}
// Fetch meta table name from registry
addListener(registry.getMetaTableName(), (metaTableName, metaError) -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Claude:

registry.getMetaTableName() is nested inside the getClusterId() callback. These two RPCs are independent. Use CompletableFuture.allOf() to issue both concurrently, saving one RTT on every connection creation. This matters for short-lived connections in tests and tools.

* callers should go through {@link #isMetaTableName(TableName)} rather than reproducing the
* check.
*/
private static final Pattern META_QUALIFIER_PATTERN = Pattern.compile("meta(_[a-z0-9]+)?");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Might be worth adding a unit test for this

# Invalid
meta_
meta_SUFFIX
meta__suffix
metatable
etc.

// available, so the dynamic name cannot be resolved here today. A follow-up should plumb the
// resolved meta TableName from the caller (e.g. via Configuration once the master has written
// it, or by passing a TableName argument from contexts that know it).
return TableName.META_TABLE_NAME;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this method be updated now that Read-Replica has been merged, or do you plan on doing is in another PR?

Comment on lines +755 to +758
// For now, it is hbase:meta because we don't support custom meta table name.
// After adding support for custom meta table names, we can calculate this from conf and use it
// downstream to persist it in Master Region.
return getDefaultMetaTableName();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also wondering this

@anmolnar

Copy link
Copy Markdown
Contributor

🤖 AI-assisted review (Claude), posted on behalf of @anmolnar as additional input.

I went through the full diff at 76fd869. The overall direction looks good, and most of the change is a mechanical replacement of META_TABLE_NAME with the connection-resolved name. A few items stood out that seem worth addressing before merge.

Likely must-fix

1. Client/server and cross-cluster compatibility
ConnectionFactory.createAsyncConnection and ClusterConnectionFactory now require registry.getMetaTableName() to succeed. An older server answers the unknown RPC with UnsupportedOperationException, so connection setup fails for:

  • new clients talking to an un-upgraded cluster,
  • upgraded RegionServers during a rolling upgrade when the bootstrap node is still old,
  • replication sinks (createAsyncClusterConnection(URI, …)) targeting an older peer.

A fallback to TableName.META_TABLE_NAME on an unknown method would cover this. Carrying the name on an existing response (e.g. GetClusterId) would also avoid an extra round trip per connection.

2. Public API compatibility
Connection.getMetaTableName() and AsyncConnection.getMetaTableName() are new abstract methods on @InterfaceAudience.Public interfaces, which breaks downstream implementations and test doubles (several mocks in this PR needed updating). Making them default methods returning TableName.META_TABLE_NAME avoids that. The new AsyncConnectionImpl / AsyncClusterConnectionImpl constructor signatures also affect custom impls loaded via hbase.client.async.connection.impl / hbase.server.cluster.connection.impl. That probably warrants a release note.

3. Suffix pattern mismatch
META_QUALIFIER_PATTERN is meta(_[a-z0-9]+)?, while VALID_META_TABLE_SUFFIX_REGEX allows [a-zA-Z0-9]+. With hbase.meta.table.suffix=Replica1, isMetaTableName(META_TABLE_NAME) returns false, so the meta table gets the wrong CellComparator and IS_META flag. Deriving one pattern from the other would keep them in sync.

Design questions

4. Storage location. MetaTableNameStore writes into the master region's info family, which holds meta location rows. tryMigrateMetaLocationsFromZooKeeper treats any row there as "migration done", so correctness now depends on it running before initMetaTableName() (no comment explains the ordering). TestMigrateAndMirrorMetaLocations also had to be relaxed. The state family used by the other *StateStore classes might be a cleaner home.

5. "A meta table" vs. "this cluster's meta table". isMetaTableName now matches any hbase:meta_*, but some callers mean "our meta":

  • ProtobufUtil.toRegionInfo returns FIRST_META_REGIONINFO (hbase:meta) for a proto naming hbase:meta_foo, silently changing the table name.
  • HMaster.isCatalogTable, TableDescriptorBuilder (IS_META) and MetaTableAccessor.getTableState treat any meta-like table as the local one.

In a shared-storage read-replica setup these become real bugs. Comparing against the resolved name at those sites would separate the two meanings.

6. Stored vs. configured name. The master persists the name on first boot, but several paths still use the static constant (FIRST_META_REGIONINFO, InitMetaProcedure.getTableName(), MasterProcedureScheduler/SchemaLocking, FSTableDescriptors). If hbase.meta.table.suffix changes later, the two silently diverge. A fail-fast check at master startup would catch that.

7. Backup masters. HMaster.getMetaTableName() stays null on backup masters and before active-master init, so clients that hit them silently fall back to hbase:meta. That's harmless today, but it will matter once custom names are supported.

Performance

CellComparatorImpl.isMetaTable(byte[]) now does TableName.valueOf() (a linear scan of tableCache) plus a regex on every call. MutableRegionInfo.containsRow/containsRange round-trip TableName → bytes → TableName each time. A getCellComparator(TableName) overload plus a Bytes.startsWith pre-check on the byte[] path would keep this cheap. The current javadoc ("avoid materializing a TableName") also doesn't match what the code does.

Minor

  • The commit message references HBASE-29881, while the PR title references HBASE-29691.
  • The meta name is cached in both MetaTableNameStore and HMaster. The store's cache and synchronized block look unnecessary.
  • initMetaTableName wraps IOException in RuntimeException, though the caller already declares IOException.
  • The comment in HBaseServerBase.getMetaTableName says persistence will come later, but this PR already persists the name.
  • The TODO: Get dynamic name from MasterServices markers could be JIRA subtasks instead.

Tests

No new tests are included. Useful additions would be:

  • isMetaTableName edge cases (uppercase suffix, meta_, metadata, non-hbase namespace),
  • a MetaTableNameStore round trip across master restart,
  • the new RPC via both the RPC and short-circuit registries,
  • a client-against-old-server test for HBASE-1015 #1.

@anmolnar
anmolnar self-requested a review November 3, 2026 17:02
Kota-SH and others added 2 commits November 12, 2026 08:03
…rom ConnectionRegistry

This is a subtask for the umbrella ticket:
HBASE-29691 Change TableName.META_TABLE_NAME from being a global static

Key changes:
- Add ConnectionRegistry.getMetaTableName() method for dynamic discovery
- Add MetaTableNameStore for persisting meta table name in master region
- Update TableName to support dynamic meta table name
- Update HMaster to integrate with MetaTableNameStore
- Update all client and server code to use dynamic meta table name
- Add protobuf changes for meta table name in Registry.proto
- Keep GetMetaTableName compatible with older servers and default the
  Connection/AsyncConnection getMetaTableName() methods
- Fix suffix regex handling in TableName and avoid per-call regex work
  in CellComparatorImpl.isMetaTable
- ProtobufUtil.toRegionInfo no longer maps every hbase:meta_* name to
  FIRST_META_REGIONINFO
- Distinguish "any meta table" from "this cluster's meta table" in
  HMaster and MetaTableAccessor; reconcile stored vs configured name and
  handle backup masters
- Store the meta table name in the master region's state family instead
  of the info (catalog) family, and drop redundant caching
- Use AsyncConnection consistently in RegionStateStore; resolve the meta
  name dynamically in FSTableDescriptors/InitMetaProcedure
- Remove ThriftConnection.getMetaTableName override that always threw
- Log/comment cleanups; add TestMetaTableNameStore and extra TableName
  and registry tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Kota-SH Kota-SH changed the title HBASE-29691: Change TableName.META_TABLE_NAME from being a global static HBASE-29881: Change TableName.META_TABLE_NAME from being a global static Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants