This is an automated email from the ASF dual-hosted git repository.
taklwu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/master by this push:
new f9a41c73ae3 HBASE-30167: Document the Read Replica feature (#8459)
f9a41c73ae3 is described below
commit f9a41c73ae3f1ba4b794f52ae63adb43523cd480
Author: Kota-SH <[email protected]>
AuthorDate: Fri Jul 10 11:13:15 2026 -0700
HBASE-30167: Document the Read Replica feature (#8459)
Signed-off-by: Andor Molnár <[email protected]>
Signed-off-by: Peng Lu <[email protected]>
Signed-off-by: Tak Lon (Stephen) Wu <[email protected]>
---
.../pages/_docs/docs/_mdx/(multi-page)/meta.json | 1 +
.../_mdx/(multi-page)/read-replica-cluster.mdx | 205 +++++++++++++++++++++
2 files changed, 206 insertions(+)
diff --git a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/meta.json
b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/meta.json
index 0c07b72bc26..9fc144368b2 100644
--- a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/meta.json
+++ b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/meta.json
@@ -36,6 +36,7 @@
"case-studies",
"---Operations & Development---",
"operational-management",
+ "read-replica-cluster",
"building-and-developing",
"unit-testing",
"---Internals---",
diff --git
a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/read-replica-cluster.mdx
b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/read-replica-cluster.mdx
new file mode 100644
index 00000000000..fa93aa31b40
--- /dev/null
+++
b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/read-replica-cluster.mdx
@@ -0,0 +1,205 @@
+---
+title: "Read Replica Cluster"
+description: "Running a secondary HBase cluster in read-only mode against
shared cloud storage to scale read workloads."
+---
+
+## Background [#read-replica-cluster-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.
+
+<Callout type="info">
+ **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.
+</Callout>
+
+The parent design lives on
[HBASE-29081](https://issues.apache.org/jira/browse/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
|
+| `RegionReadOnlyController` | Region | put, delete,
batchMutate, checkAnd\*, append, increment, flush, compaction, WAL append,
commit/replay |
+| `RegionServerReadOnlyController` | RegionServer | WAL roll, replication
sink mutations, log 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 an
+ `IOException`.
+- 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 Mode`
and 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`:
+
+```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
+
+1. Provision the replica cluster on hardware that can reach the active
cluster's `hbase.rootdir` (typically
+ the same HDFS or object store).
+2. Set `hbase.global.readonly.enabled=true` in the replica's `hbase-site.xml`.
And set up
+ `hbase.meta.table.suffix`, to distinguish the replica cluster's meta table
on the shared storage.
+3. Start the cluster and verify if the Master log shows
+ `[Read-replica feature] Replica cluster is being started in Read Only Mode`.
+4. From the replica shell, run `refresh_meta` and then `refresh_hfiles` to
materialize the active cluster's
+ current state on the replica.
+
+### Case 2. Routine sync after writes on the active cluster
+
+```ruby
+# On the active cluster
+hbase> flush 'my_namespace:my_table'
+```
+
+```ruby
+# 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:
+
+```ruby
+hbase> refresh_hfiles # all user tables
+hbase> refresh_hfiles 'TABLE_NAME' => 'ns:table' # one table
+hbase> refresh_hfiles 'NAMESPACE' => 'ns' # one namespace
+```
+
+Passing 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:
+
+```java
+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 namespace
+```
+
+### Case 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.id` file
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
+
+1. Confirm the original active cluster is fully down.
+2. If a stale `active.cluster.suffix.id` from 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.
+3. Set `hbase.global.readonly.enabled=false` on the replica and apply the
change (dynamic update or
+ restart). The master writes a fresh sentinel file with this cluster's
identity.
+
+## 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_hfiles`<br />`refresh_hfiles 'TABLE_NAME' =>
'tablename'`<br />`refresh_hfiles 'TABLE_NAME' => 'namespace:test_table'`<br
/>`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. |