Read Replica Cluster
Running a secondary HBase cluster in read-only mode against shared cloud storage to scale read workloads.
Background
A Read Replica Cluster is an entire HBase cluster running in global read-only mode against the same shared
storage (hbase.rootdir) as an active read-write cluster. Both clusters list the same HFiles in the same
HDFS / cloud-object-store location; no data is copied. Reads can be served from either cluster, letting the
read workload be fanned out across multiple clusters without doubling storage cost.
Typical use cases:
- Fan out heavy scan / analytical workloads off the primary cluster.
- Add cross-availability-zone read capacity backed by a single shared bucket.
- Stand up an isolated cluster for read-mostly experiments without copying data.
Eventual consistency. A replica only sees data once (a) the active cluster has flushed the
data to HFiles in shared storage, and (b) the replica has been told to re-read shared storage via
the refresh_meta and refresh_hfiles commands. MemStore data on the active cluster is invisible
to the replica until flushed.
The parent design lives on HBASE-29081.
Design
The feature has three parts.
Custom hbase:meta per cluster
Every cluster sharing a hbase.rootdir needs its own hbase:meta and its own master local region
directory, because region assignments and master-local state are node-scoped and cannot be shared. Other
system tables (hbase:acl, hbase:replication) are safe to share because their contents are storage-wide
and the replica never writes to them.
The configuration key hbase.meta.table.suffix selects a per-cluster suffix; the meta table becomes
hbase:meta_<suffix> and the master's local store directory becomes MasterData_<suffix>. Each cluster
sharing the same hbase.rootdir must be configured with a distinct suffix so its hbase:meta and
MasterData directory do not collide with any other cluster's. The suffix must match [a-zA-Z0-9]+.
Global read-only mode
hbase.global.readonly.enabled=true puts a cluster into read-only mode. Five coprocessor controllers under
org.apache.hadoop.hbase.security.access intercept every user-table mutation path and throw
WriteAttemptedOnReadOnlyClusterException (a DoNotRetryIOException) with the message
Operation not allowed in Read-Only Mode:
| Class | Coprocessor host | Blocks |
|---|---|---|
MasterReadOnlyController | Master | DDL, snapshots, splits, merges, namespace ops, ACL/quota ops, replication-peer ops |
RegionServerReadOnlyController | RegionServer | WAL roll, replication sink mutations, log replay |
RegionReadOnlyController | Region | put, delete, batchMutate, checkAnd*, append, increment, flush, compaction, WAL append, commit/replay |
BulkLoadReadOnlyController | Region | bulk-load prepare/cleanup |
EndpointReadOnlyController | Region | all coprocessor endpoint invocations |
Operators do not load these classes manually. CoprocessorConfigurationUtil.syncReadOnlyConfigurations
adds them to hbase.coprocessor.master.classes, hbase.coprocessor.regionserver.classes, and
hbase.coprocessor.region.classes at startup and on every dynamic
ConfigurationManager.notifyAllObservers event — so the flag can be flipped at runtime with
update_all_config (see Case 3).
Preventing Multiple Active Clusters (active.cluster.suffix.id)
Two clusters writing to the same hbase.rootdir would corrupt shared storage. To enforce a single writer, an
active master creates a protobuf-serialized sentinel at <hbase.rootdir>/active.cluster.suffix.id recording
its cluster ID and meta suffix. MasterFileSystem.negotiateActiveClusterSuffixFile runs at master startup:
- An active cluster (
hbase.global.readonly.enabled=false) creates the file if absent, or verifies its contents match its own identity. If the file belongs to another cluster, startup aborts with anIOException. - A replica cluster (
hbase.global.readonly.enabled=true) does not read or write the file; it logs[Read-replica feature] Replica cluster is being started in Read Only Modeand continues.
AbstractReadOnlyController.manageActiveClusterIdFile handles the dynamic toggle: switching to read-only
deletes the file if this cluster owns it, and switching back to read-write creates the file if absent.
Configuration
On every node of the read replica cluster, add the following to hbase-site.xml:
<property>
<name>hbase.global.readonly.enabled</name>
<value>true</value>
<description>
Put this cluster into global read-only mode. All user-table writes, flushes,
compactions, splits, and merges are blocked. The five ReadOnly coprocessor
controllers are loaded automatically.
</description>
</property>
<property>
<name>hbase.meta.table.suffix</name>
<value>replica1</value>
<description>
Optional. If set, the meta table is named hbase:meta_<suffix> and the
master's local store directory is MasterData_<suffix>. Value must match
[a-zA-Z0-9]+. Each cluster sharing the same hbase.rootdir MUST be
configured with a distinct suffix so its hbase:meta and MasterData
directory do not collide with any other cluster's.
</description>
</property>The active cluster uses the same hbase.rootdir but its own hbase.meta.table.suffix (distinct from
every replica's suffix), and leaves hbase.global.readonly.enabled unset or false.
hbase.global.readonly.enabled is a dynamic configuration — a config-change event reloads the read-only
coprocessors without restarting the process. All nodes must agree on the value; operators are responsible for
keeping every hbase-site.xml in sync before issuing update_all_config.
Operation and maintenance
Case 1. Bring up a new read replica cluster
- Provision the replica cluster on hardware that can reach the active cluster's
hbase.rootdir(typically the same HDFS or object store). - Set
hbase.global.readonly.enabled=truein the replica'shbase-site.xml. And set uphbase.meta.table.suffix, to distinguish the replica cluster's meta table on the shared storage. - Start the cluster and verify if the Master log shows
[Read-replica feature] Replica cluster is being started in Read Only Mode. - From the replica shell, run
refresh_metaand thenrefresh_hfilesto materialize the active cluster's current state on the replica.
Case 2. Routine sync after writes on the active cluster
# On the active cluster
hbase> flush 'my_namespace:my_table'# On the read replica cluster
hbase> refresh_meta
hbase> refresh_hfiles 'TABLE_NAME' => 'my_namespace:my_table'Always run refresh_meta first, then refresh_hfiles. refresh_hfiles only refreshes regions that are open
on the replica, so newly discovered regions must be in meta (and assigned) before their HFiles can be picked
up. refresh_hfiles supports three scopes:
hbase> refresh_hfiles # all user tables
hbase> refresh_hfiles 'TABLE_NAME' => 'ns:table' # one table
hbase> refresh_hfiles 'NAMESPACE' => 'ns' # one namespacePassing both TABLE_NAME and NAMESPACE to refresh_hfiles is rejected. Both commands return a procedure
ID that can be tracked through the master UI or Admin.getProcedures().
If the replica's block cache holds stale entries for a table that has just been refreshed, evict them with
the pre-existing clear_block_cache 'my_namespace:my_table' shell command.
Admin and AsyncAdmin expose the same operations programmatically:
long pid;
pid = admin.refreshMeta();
pid = admin.refreshHFiles(); // all user tables
pid = admin.refreshHFiles(TableName.valueOf("ns:table")); // one table
pid = admin.refreshHFiles("ns"); // one namespaceCase 3. Dynamically toggle read-only mode
hbase.global.readonly.enabled can be changed without a restart. Edit hbase-site.xml then
trigger a configuration refresh (for example, update_all_config from the shell). ConfigurationManager
notifies its observers, which load or unload the read-only coprocessors and call
AbstractReadOnlyController.manageActiveClusterIdFile:
- false → true (becoming a replica): the
active.cluster.suffix.idfile is deleted only if its contents match this cluster; if another cluster owns the file, it is left in place. - true → false (becoming active): the file is recreated with this cluster's identity, unless it already exists.
In-flight batch operations are not interrupted; write operations submitted after the toggle throw
WriteAttemptedOnReadOnlyClusterException. The caller is responsible for handling and (if desired) resubmitting the failed
mutations.
Case 4. Promote a replica when the active cluster is lost
- Confirm the original active cluster is fully down.
- If a stale
active.cluster.suffix.idfrom the previous active is still present, remove it manually (e.g.hdfs dfs -rm <hbase.rootdir>/active.cluster.suffix.id, or the equivalent CLI for your object store). The new active master will refuse to start while a foreign sentinel file is in place. - Set
hbase.global.readonly.enabled=falseon the replica and apply the change (dynamic update or restart). The master writes a fresh sentinel file with this cluster's identity.
Case 5. Recover from a blocked read-only transition
If you attempt to promote a replica to active (hbase.global.readonly.enabled=false + update_all_config)
while a different cluster's active.cluster.suffix.id file is still present, the transition is blocked. The
shell returns a ReadOnlyTransitionException and the cluster remains in read-only mode — writes continue to be
rejected with WriteAttemptedOnReadOnlyClusterException.
The server ERROR log includes the foreign cluster's ID:
Cannot disable read-only mode. The active.cluster.suffix.id file contains a different
cluster ID (<foreign-id>), which means that cluster is already the active cluster.
Reverting hbase.global.readonly.enabled to true.A cluster whose promotion was blocked is still a replica — the read-only coprocessors remain
loaded and writes are still rejected — even though its hbase-site.xml has
hbase.global.readonly.enabled=false.
To recover, choose one of the following paths depending on whether the existing active cluster is still running.
Path A — the active cluster is still running:
- On the currently active cluster, set
hbase.global.readonly.enabled=trueinhbase-site.xmland runupdate_all_config. This converts it to a replica and deletes itsactive.cluster.suffix.idfile. - On the cluster you want to promote, re-run
update_all_config. The transition will now succeed — the cluster writes a fresh sentinel file with its own identity and unloads the read-only coprocessors.
Path B — the active cluster is down or has already been converted to a replica:
- Confirm the cluster that owns the sentinel file is fully stopped or has already been converted to a replica.
- Remove the foreign sentinel file:
hdfs dfs -rm <hbase.rootdir>/active.cluster.suffix.id - On the cluster you want to promote, re-run
update_all_config. The transition will now succeed — the cluster writes a fresh sentinel file with its own identity and unloads the read-only coprocessors.
Do not remove the sentinel file while the cluster that wrote it is still running as an active cluster. Doing so would allow two clusters to write to the same shared storage simultaneously, risking data corruption.
Configurations and Commands
New configs
| Config | Default | Description |
|---|---|---|
hbase.meta.table.suffix | "" | Adds a suffix to the meta table name. value='test' produces the table name hbase:meta_test. |
hbase.global.readonly.enabled | false | Puts the entire cluster into read-only mode. |
New commands
| Command | Usage | Description |
|---|---|---|
refresh_hfiles | refresh_hfilesrefresh_hfiles 'TABLE_NAME' => 'tablename'refresh_hfiles 'TABLE_NAME' => 'namespace:test_table'refresh_hfiles 'NAMESPACE' => 'namespace' | Refreshes HFiles from disk. Used to pick up new edits on the read replica. |
refresh_meta | refresh_meta | Syncs the meta table with the backing storage. Used to pick up new tables and regions. |