Skip to content

Commit a8e656e

Browse files
committed
feat(replication): map HBase table names to Cloud Bigtable table IDs
1 parent 292cfa5 commit a8e656e

8 files changed

Lines changed: 993 additions & 20 deletions

File tree

‎hbase-migration-tools/bigtable-hbase-replication/README.md‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,57 @@ Please refer
143143
to [HBaseToCloudBigtableReplicationConfiguration](bigtable-hbase-replication-core/src/main/java/com/google/cloud/bigtable/hbase/replication/configuration/HBaseToCloudBigtableReplicationConfiguration.java)
144144
for other properties that can be set.
145145

146+
### Mapping HBase table names to Cloud Bigtable table IDs
147+
148+
By default a WAL entry for HBase table `t` is written to the Cloud Bigtable
149+
table `t`. Cloud Bigtable does not have namespaces and `:` is not a valid
150+
character in a Cloud Bigtable table ID, so HBase tables in a non-default
151+
namespace (for example `my_namespace:my_table`) can not be replicated without
152+
renaming them.
153+
154+
The endpoint accepts mappings from HBase table names to Cloud Bigtable
155+
table IDs. Source HBase table names are normalized to canonical form (for
156+
example, `default:my_table` and `my_table` refer to the same HBase table).
157+
Mappings must be injective (1:1): multiple HBase tables cannot map to the same
158+
Cloud Bigtable table ID.
159+
160+
Every target must be a valid Cloud Bigtable table ID (`[_a-zA-Z0-9][-_.a-zA-Z0-9]*`,
161+
at most 50 characters); the endpoint refuses to start if the mapping is
162+
malformed or contains collisions, ensuring mutations are never replicated to the
163+
wrong table. Mapping targets are also reserved at runtime: if an unmapped HBase
164+
table has the same canonical name as a configured mapping target (for example,
165+
`ns:table_B` maps to `table_B` while an unmapped `default:table_B` is also
166+
replicated), `replicate()` fails the batch and halts replication for that peer
167+
until an explicit disambiguating mapping (such as `"table_B": "table_B_default"`)
168+
is added.
169+
170+
The mapping is configured through `google.bigtable.replication.table_name_mapping`
171+
in `hbase-site.xml` using a JSON object of
172+
`{"source_hbase_table": "destination_bigtable_table"}` pairs (using the same
173+
JSON structure as the mapping file accepted by the
174+
[HBase schema translator](../../bigtable-hbase-1.x-parent/bigtable-hbase-1.x-tools)):
175+
176+
```xml
177+
<property>
178+
<name>google.bigtable.replication.table_name_mapping</name>
179+
<value>{"my_namespace:my_table": "my_namespace_my_table", "other_namespace:events": "events"}</value>
180+
</property>
181+
```
182+
183+
Note that `TABLE_CFS` in `add_peer` only *selects* the tables and column
184+
families that are replicated to a peer; it can not rename them. Use `TABLE_CFS`
185+
with the HBase table names (for example `TABLE_CFS => { 'my_namespace:my_table'
186+
=> [] }`) and the mapping above to choose the Cloud Bigtable table ID.
187+
188+
**Operational notes:**
189+
* The mapping is loaded when the replication endpoint starts, so changing
190+
`google.bigtable.replication.table_name_mapping` requires a rolling restart of
191+
the HBase region servers.
192+
* After a restart, any un-replicated WAL entries still queued for the peer are
193+
routed using the new mapping. Pause the replication peer or wait for
194+
replication to catch up before remapping a table that is actively receiving
195+
writes.
196+
146197
### Bidirectional Replication with Cloud Bigtable
147198

148199
The bidirectional replication feature filters out mutations replicated to Hbase from Cloud Bigtable change streams with the [Bigtable-Hbase replicator](https://github.com/GoogleCloudPlatform/DataflowTemplates/tree/main/v2/bigtable-cdc-to-hbase) to prevent replication loops.

‎hbase-migration-tools/bigtable-hbase-replication/bigtable-hbase-1.x-replication/src/test/java/com/google/cloud/bigtable/hbase1_x/replication/HbaseToCloudBigtableReplicationEndpointTest.java‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.bigtable.hbase1_x.replication;
1818

19+
import static com.google.cloud.bigtable.hbase.replication.configuration.HBaseToCloudBigtableReplicationConfiguration.TABLE_NAME_MAPPING_KEY;
1920
import static com.google.cloud.bigtable.hbase.replication.utils.TestUtils.CF1;
2021
import static com.google.cloud.bigtable.hbase.replication.utils.TestUtils.FILTERED_ROW_KEY;
2122
import static com.google.cloud.bigtable.hbase.replication.utils.TestUtils.ROW_KEY;
@@ -36,6 +37,8 @@
3637
import org.apache.hadoop.hbase.HColumnDescriptor;
3738
import org.apache.hadoop.hbase.HConstants;
3839
import org.apache.hadoop.hbase.HTableDescriptor;
40+
import org.apache.hadoop.hbase.NamespaceDescriptor;
41+
import org.apache.hadoop.hbase.NamespaceExistException;
3942
import org.apache.hadoop.hbase.TableName;
4043
import org.apache.hadoop.hbase.client.Append;
4144
import org.apache.hadoop.hbase.client.Connection;
@@ -121,6 +124,11 @@ public Entry filter(Entry entry) {
121124
private Table cbtTable;
122125
private Table cbtTable2;
123126

127+
private static final TableName REMAPPED_SOURCE_TABLE =
128+
TableName.valueOf("my_ns", "remapped_source_table");
129+
private static final TableName REMAPPED_TARGET_TABLE =
130+
TableName.valueOf("my_ns_remapped_target_table");
131+
124132
@BeforeClass
125133
public static void setUpCluster() throws Exception {
126134
// Prepare HBase mini cluster configuration
@@ -131,6 +139,11 @@ public static void setUpCluster() throws Exception {
131139
conf.set("google.bigtable.project.id", "test-project");
132140
// This config will connect Replication endpoint to the emulator and not the prod CBT.
133141
conf.set("google.bigtable.emulator.endpoint.host", "localhost:" + bigtableEmulator.getPort());
142+
conf.set(
143+
TABLE_NAME_MAPPING_KEY,
144+
String.format(
145+
"{\"%s\": \"%s\"}",
146+
REMAPPED_SOURCE_TABLE.getNameAsString(), REMAPPED_TARGET_TABLE.getNameAsString()));
134147

135148
hbaseTestingUtil.startMiniCluster(2);
136149
replicationAdmin = new ReplicationAdmin(hbaseTestingUtil.getConfiguration());
@@ -500,4 +513,65 @@ public void testHBaseCBTTimestampTruncation() throws IOException, InterruptedExc
500513
hbaseCells.get(0).getTimestamp(),
501514
bigtableCells.get(0).getTimestamp());
502515
}
516+
517+
private void createRemappedTables(TableName hbaseSourceTable, TableName cbtTargetTable)
518+
throws IOException {
519+
String namespace = hbaseSourceTable.getNamespaceAsString();
520+
if (!NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR.equals(namespace)) {
521+
try {
522+
hbaseConnection.getAdmin().createNamespace(NamespaceDescriptor.create(namespace).build());
523+
} catch (NamespaceExistException ignored) {
524+
// Namespace already exists
525+
}
526+
}
527+
528+
HTableDescriptor hbaseHtd = new HTableDescriptor(hbaseSourceTable);
529+
HColumnDescriptor cf1 = new HColumnDescriptor(TestUtils.CF1);
530+
cf1.setMaxVersions(100);
531+
cf1.setScope(HConstants.REPLICATION_SCOPE_GLOBAL);
532+
hbaseHtd.addFamily(cf1);
533+
HColumnDescriptor cf2 = new HColumnDescriptor(TestUtils.CF2);
534+
cf2.setMaxVersions(100);
535+
cf2.setScope(HConstants.REPLICATION_SCOPE_GLOBAL);
536+
hbaseHtd.addFamily(cf2);
537+
hbaseTestingUtil.getHBaseAdmin().createTable(hbaseHtd);
538+
539+
HTableDescriptor cbtHtd = new HTableDescriptor(cbtTargetTable);
540+
HColumnDescriptor cbtCf1 = new HColumnDescriptor(TestUtils.CF1);
541+
cbtCf1.setMaxVersions(100);
542+
cbtHtd.addFamily(cbtCf1);
543+
HColumnDescriptor cbtCf2 = new HColumnDescriptor(TestUtils.CF2);
544+
cbtCf2.setMaxVersions(100);
545+
cbtHtd.addFamily(cbtCf2);
546+
cbtConnection.getAdmin().createTable(cbtHtd);
547+
}
548+
549+
@Test
550+
public void testTableNameMapping() throws Exception {
551+
createRemappedTables(REMAPPED_SOURCE_TABLE, REMAPPED_TARGET_TABLE);
552+
553+
try (Table srcTable = hbaseConnection.getTable(REMAPPED_SOURCE_TABLE);
554+
Table dstTable = cbtConnection.getTable(REMAPPED_TARGET_TABLE)) {
555+
TestReplicationEndpoint.replicatedEntries.set(0);
556+
557+
for (int i = 0; i < 25; i++) {
558+
Put put = new Put(TestUtils.getRowKey(i));
559+
put.addColumn(TestUtils.CF1, TestUtils.COL_QUALIFIER, 0, TestUtils.getValue(i));
560+
put.addColumn(TestUtils.CF2, TestUtils.COL_QUALIFIER, 0, TestUtils.getValue(100 + i));
561+
srcTable.put(put);
562+
}
563+
564+
TestUtils.assertTableEventuallyEquals(
565+
srcTable, dstTable, () -> TestReplicationEndpoint.replicatedEntries.get() >= 25);
566+
} finally {
567+
if (hbaseTestingUtil.getHBaseAdmin().tableExists(REMAPPED_SOURCE_TABLE)) {
568+
hbaseTestingUtil.getHBaseAdmin().disableTable(REMAPPED_SOURCE_TABLE);
569+
hbaseTestingUtil.getHBaseAdmin().deleteTable(REMAPPED_SOURCE_TABLE);
570+
}
571+
if (cbtConnection.getAdmin().tableExists(REMAPPED_TARGET_TABLE)) {
572+
cbtConnection.getAdmin().disableTable(REMAPPED_TARGET_TABLE);
573+
cbtConnection.getAdmin().deleteTable(REMAPPED_TARGET_TABLE);
574+
}
575+
}
576+
}
503577
}

‎hbase-migration-tools/bigtable-hbase-replication/bigtable-hbase-2.x-replication/src/test/java/com/google/cloud/bigtable/hbase2_x/replication/HbaseToCloudBigtableReplicationEndpointTest.java‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.bigtable.hbase2_x.replication;
1818

19+
import static com.google.cloud.bigtable.hbase.replication.configuration.HBaseToCloudBigtableReplicationConfiguration.TABLE_NAME_MAPPING_KEY;
1920
import static com.google.cloud.bigtable.hbase.replication.utils.TestUtils.CF1;
2021
import static com.google.cloud.bigtable.hbase.replication.utils.TestUtils.CF2;
2122
import static com.google.cloud.bigtable.hbase.replication.utils.TestUtils.FILTERED_ROW_KEY;
@@ -36,6 +37,8 @@
3637
import org.apache.hadoop.hbase.HColumnDescriptor;
3738
import org.apache.hadoop.hbase.HConstants;
3839
import org.apache.hadoop.hbase.HTableDescriptor;
40+
import org.apache.hadoop.hbase.NamespaceDescriptor;
41+
import org.apache.hadoop.hbase.NamespaceExistException;
3942
import org.apache.hadoop.hbase.TableName;
4043
import org.apache.hadoop.hbase.client.Append;
4144
import org.apache.hadoop.hbase.client.Connection;
@@ -121,6 +124,11 @@ public Entry filter(Entry entry) {
121124
private Table cbtTable;
122125
private Table cbtTable2;
123126

127+
private static final TableName REMAPPED_SOURCE_TABLE =
128+
TableName.valueOf("my_ns", "remapped_source_table");
129+
private static final TableName REMAPPED_TARGET_TABLE =
130+
TableName.valueOf("my_ns_remapped_target_table");
131+
124132
@BeforeClass
125133
public static void setUpCluster() throws Exception {
126134
// Prepare HBase mini cluster configuration
@@ -131,6 +139,11 @@ public static void setUpCluster() throws Exception {
131139
conf.set("google.bigtable.project.id", "test-project");
132140
// This config will connect Replication endpoint to the emulator and not the prod CBT.
133141
conf.set("google.bigtable.emulator.endpoint.host", "localhost:" + bigtableEmulator.getPort());
142+
conf.set(
143+
TABLE_NAME_MAPPING_KEY,
144+
String.format(
145+
"{\"%s\": \"%s\"}",
146+
REMAPPED_SOURCE_TABLE.getNameAsString(), REMAPPED_TARGET_TABLE.getNameAsString()));
134147

135148
hbaseTestingUtil.startMiniCluster(2);
136149
replicationAdmin = new ReplicationAdmin(hbaseTestingUtil.getConfiguration());
@@ -494,4 +507,65 @@ public void testHBaseCBTTimestampTruncation() throws IOException, InterruptedExc
494507
hbaseCells.get(0).getTimestamp(),
495508
bigtableCells.get(0).getTimestamp());
496509
}
510+
511+
private void createRemappedTables(TableName hbaseSourceTable, TableName cbtTargetTable)
512+
throws IOException {
513+
String namespace = hbaseSourceTable.getNamespaceAsString();
514+
if (!NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR.equals(namespace)) {
515+
try {
516+
hbaseConnection.getAdmin().createNamespace(NamespaceDescriptor.create(namespace).build());
517+
} catch (NamespaceExistException ignored) {
518+
// Namespace already exists
519+
}
520+
}
521+
522+
HTableDescriptor hbaseHtd = new HTableDescriptor(hbaseSourceTable);
523+
HColumnDescriptor cf1 = new HColumnDescriptor(TestUtils.CF1);
524+
cf1.setMaxVersions(100);
525+
cf1.setScope(HConstants.REPLICATION_SCOPE_GLOBAL);
526+
hbaseHtd.addFamily(cf1);
527+
HColumnDescriptor cf2 = new HColumnDescriptor(CF2);
528+
cf2.setMaxVersions(100);
529+
cf2.setScope(HConstants.REPLICATION_SCOPE_GLOBAL);
530+
hbaseHtd.addFamily(cf2);
531+
hbaseTestingUtil.getHBaseAdmin().createTable(hbaseHtd);
532+
533+
HTableDescriptor cbtHtd = new HTableDescriptor(cbtTargetTable);
534+
HColumnDescriptor cbtCf1 = new HColumnDescriptor(TestUtils.CF1);
535+
cbtCf1.setMaxVersions(100);
536+
cbtHtd.addFamily(cbtCf1);
537+
HColumnDescriptor cbtCf2 = new HColumnDescriptor(CF2);
538+
cbtCf2.setMaxVersions(100);
539+
cbtHtd.addFamily(cbtCf2);
540+
cbtConnection.getAdmin().createTable(cbtHtd);
541+
}
542+
543+
@Test
544+
public void testTableNameMapping() throws Exception {
545+
createRemappedTables(REMAPPED_SOURCE_TABLE, REMAPPED_TARGET_TABLE);
546+
547+
try (Table srcTable = hbaseConnection.getTable(REMAPPED_SOURCE_TABLE);
548+
Table dstTable = cbtConnection.getTable(REMAPPED_TARGET_TABLE)) {
549+
TestReplicationEndpoint.replicatedEntries.set(0);
550+
551+
for (int i = 0; i < 25; i++) {
552+
Put put = new Put(TestUtils.getRowKey(i));
553+
put.addColumn(TestUtils.CF1, TestUtils.COL_QUALIFIER, 0, TestUtils.getValue(i));
554+
put.addColumn(CF2, TestUtils.COL_QUALIFIER, 0, TestUtils.getValue(100 + i));
555+
srcTable.put(put);
556+
}
557+
558+
TestUtils.assertTableEventuallyEquals(
559+
srcTable, dstTable, () -> TestReplicationEndpoint.replicatedEntries.get() >= 25);
560+
} finally {
561+
if (hbaseTestingUtil.getHBaseAdmin().tableExists(REMAPPED_SOURCE_TABLE)) {
562+
hbaseTestingUtil.getHBaseAdmin().disableTable(REMAPPED_SOURCE_TABLE);
563+
hbaseTestingUtil.getHBaseAdmin().deleteTable(REMAPPED_SOURCE_TABLE);
564+
}
565+
if (cbtConnection.getAdmin().tableExists(REMAPPED_TARGET_TABLE)) {
566+
cbtConnection.getAdmin().disableTable(REMAPPED_TARGET_TABLE);
567+
cbtConnection.getAdmin().deleteTable(REMAPPED_TARGET_TABLE);
568+
}
569+
}
570+
}
497571
}

0 commit comments

Comments
 (0)