This is an automated email from the ASF dual-hosted git repository.

yuxiqian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-cdc.git


The following commit(s) were added to refs/heads/master by this push:
     new a52fe0dce [FLINK-40159][connect/hudi] Support Hive sync for Hudi 
pipeline sink (#4476)
a52fe0dce is described below

commit a52fe0dcedc9e9f32add35e0f4105d661170b106
Author: BreadS <[email protected]>
AuthorDate: Tue Jul 21 17:29:34 2026 +0800

    [FLINK-40159][connect/hudi] Support Hive sync for Hudi pipeline sink (#4476)
    
    * [FLINK-40159][connect/hudi] Support Hive sync for Hudi pipeline sink
    
    * Default value adjustment
---
 .../MultiTableStreamWriteOperatorCoordinator.java  | 95 ++++++++++++++++++++--
 ...ltiTableStreamWriteOperatorCoordinatorTest.java | 66 +++++++++++++++
 2 files changed, 155 insertions(+), 6 deletions(-)

diff --git 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-hudi/src/main/java/org/apache/flink/cdc/connectors/hudi/sink/coordinator/MultiTableStreamWriteOperatorCoordinator.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-hudi/src/main/java/org/apache/flink/cdc/connectors/hudi/sink/coordinator/MultiTableStreamWriteOperatorCoordinator.java
index 5396f6f66..161364e53 100644
--- 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-hudi/src/main/java/org/apache/flink/cdc/connectors/hudi/sink/coordinator/MultiTableStreamWriteOperatorCoordinator.java
+++ 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-hudi/src/main/java/org/apache/flink/cdc/connectors/hudi/sink/coordinator/MultiTableStreamWriteOperatorCoordinator.java
@@ -45,15 +45,20 @@ import org.apache.hudi.common.util.Option;
 import org.apache.hudi.common.util.SerializationUtils;
 import org.apache.hudi.common.util.collection.Pair;
 import org.apache.hudi.configuration.FlinkOptions;
+import org.apache.hudi.configuration.HadoopConfigurations;
 import org.apache.hudi.configuration.OptionsResolver;
 import org.apache.hudi.exception.HoodieException;
+import org.apache.hudi.hadoop.fs.HadoopFSUtils;
+import org.apache.hudi.hive.HiveSyncTool;
 import org.apache.hudi.sink.StreamWriteOperatorCoordinator;
 import org.apache.hudi.sink.event.Correspondent;
 import org.apache.hudi.sink.event.WriteMetadataEvent;
 import org.apache.hudi.sink.utils.CoordinationResponseSerDe;
 import org.apache.hudi.sink.utils.EventBuffers;
 import org.apache.hudi.sink.utils.ExplicitClassloaderThreadFactory;
+import org.apache.hudi.sink.utils.HiveSyncContext;
 import org.apache.hudi.sink.utils.NonThrownExecutor;
+import org.apache.hudi.storage.StorageConfiguration;
 import org.apache.hudi.util.ClusteringUtil;
 import org.apache.hudi.util.CompactionUtil;
 import org.apache.hudi.util.FlinkWriteClients;
@@ -61,6 +66,8 @@ import org.apache.hudi.util.StreamerUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nullable;
+
 import java.io.IOException;
 import java.io.Serializable;
 import java.nio.file.Files;
@@ -107,16 +114,19 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
         final EventBuffers eventBuffers;
         final TableState tableState;
         final String tablePath;
+        @Nullable final transient HiveSyncContext hiveSyncContext;
 
         TableContext(
                 HoodieFlinkWriteClient<?> writeClient,
                 EventBuffers eventBuffers,
                 TableState tableState,
-                String tablePath) {
+                String tablePath,
+                @Nullable HiveSyncContext hiveSyncContext) {
             this.writeClient = writeClient;
             this.eventBuffers = eventBuffers;
             this.tableState = tableState;
             this.tablePath = tablePath;
+            this.hiveSyncContext = hiveSyncContext;
         }
 
         void close() {
@@ -139,6 +149,7 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
         final boolean scheduleCompaction;
         final boolean scheduleClustering;
         final boolean isDeltaTimeCompaction;
+        final boolean syncHive;
 
         // Event-driven compaction tracking - tracks actual write activity
         long commitsSinceLastCompaction = 0;
@@ -159,6 +170,7 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
             this.scheduleClustering = 
OptionsResolver.needsScheduleClustering(conf);
             this.isDeltaTimeCompaction = 
OptionsResolver.isDeltaTimeCompaction(conf);
             this.commitsThreshold = conf.get(COMPACTION_DELTA_COMMITS);
+            this.syncHive = conf.get(FlinkOptions.HIVE_SYNC_ENABLED);
         }
 
         /**
@@ -252,6 +264,9 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
     /** A single-thread executor to handle instant time requests, mimicking 
the parent behavior. */
     private transient NonThrownExecutor instantRequestExecutor;
 
+    /** A single-thread executor to handle asynchronous hive sync. */
+    @Nullable private transient NonThrownExecutor hiveSyncExecutor;
+
     public MultiTableStreamWriteOperatorCoordinator(Configuration conf, 
Context context) {
         super(conf, context);
         this.baseConfig = conf;
@@ -296,6 +311,19 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
         // Initialize the gateways array to avoid NullPointerException when 
subtasks are ready.
         this.gateways = new SubtaskGateway[context.currentParallelism()];
 
+        // Initialize the hive sync executor if hive sync is enabled.
+        if (baseConfig.get(FlinkOptions.HIVE_SYNC_ENABLED)) {
+            this.hiveSyncExecutor =
+                    NonThrownExecutor.builder(LOG)
+                            .threadFactory(
+                                    new ExplicitClassloaderThreadFactory(
+                                            "multi-table-hive-sync",
+                                            context.getUserCodeClassloader()))
+                            .waitForTasksFinish(true)
+                            .build();
+            LOG.info("Hive sync executor initialized.");
+        }
+
         // Re-initialize transient fields after deserialization from a Flink 
checkpoint.
         // When the coordinator is restored, the `tableContexts` map is 
deserialized, but all
         // `writeClient` fields within it will be null because they are 
transient.
@@ -312,13 +340,16 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
 
                 // Replace the old context (with a null client) with a new one 
containing the live
                 // client.
+                HiveSyncContext hiveSyncCtx =
+                        createHiveSyncContextIfNeeded(tableConfig, 
oldContext.tableState);
                 tableContexts.put(
                         tableId,
                         new TableContext(
                                 writeClient,
                                 oldContext.eventBuffers,
                                 oldContext.tableState,
-                                oldContext.tablePath));
+                                oldContext.tablePath,
+                                hiveSyncCtx));
                 LOG.info(
                         "Successfully re-initialized write client for 
recovered table: {}",
                         tableId);
@@ -477,13 +508,19 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
                                         
FlinkWriteClients.createWriteClient(tableConfig);
                                 TableState tableState = new 
TableState(tableConfig);
                                 EventBuffers eventBuffers = 
EventBuffers.getInstance(tableConfig);
+                                HiveSyncContext hiveSyncCtx =
+                                        
createHiveSyncContextIfNeeded(tableConfig, tableState);
 
                                 LOG.info(
                                         "Successfully initialized resources 
for table: {} at path: {}",
                                         tId,
                                         tablePath);
                                 return new TableContext(
-                                        writeClient, eventBuffers, tableState, 
tablePath);
+                                        writeClient,
+                                        eventBuffers,
+                                        tableState,
+                                        tablePath,
+                                        hiveSyncCtx);
                             } catch (Exception e) {
                                 LOG.error(
                                         "Failed to initialize Hudi table 
resources for: {}",
@@ -552,12 +589,15 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
 
             // Update the table context with the new write client
             // Keep the same eventBuffers, tableState, and tablePath
+            HiveSyncContext hiveSyncCtx =
+                    createHiveSyncContextIfNeeded(tableConfig, 
oldContext.tableState);
             TableContext newContext =
                     new TableContext(
                             newWriteClient,
                             oldContext.eventBuffers,
                             oldContext.tableState,
-                            oldContext.tablePath);
+                            oldContext.tablePath,
+                            hiveSyncCtx);
             tableContexts.put(tableId, newContext);
 
             LOG.info("Successfully updated write client for table {} after 
schema change", tableId);
@@ -694,7 +734,7 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
                                     EventBuffers eventBuffers =
                                             
EventBuffers.getInstance(tableConfig);
                                     return new TableContext(
-                                            null, eventBuffers, tableState, 
tablePath);
+                                            null, eventBuffers, tableState, 
tablePath, null);
                                 });
                         TableContext tableContext = tableContexts.get(tableId);
                         
tableContext.eventBuffers.addEventsToBuffer(completedEvents);
@@ -842,6 +882,8 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
                         "Skipping table services scheduling for table [{}] - 
empty commit",
                         tableId);
             }
+
+            syncHiveAsyncIfNeeded(tableId, tableContext);
         } else {
             LOG.error("Failed to commit instant [{}] for table [{}]", instant, 
tableId);
             MultiTableStreamWriteOperatorCoordinator.this.context.failJob(
@@ -909,11 +951,41 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
         }
     }
 
+    private void syncHiveAsyncIfNeeded(TableId tableId, TableContext 
tableContext) {
+        if (tableContext.tableState.syncHive
+                && tableContext.hiveSyncContext != null
+                && hiveSyncExecutor != null) {
+            hiveSyncExecutor.execute(
+                    () -> {
+                        try (HiveSyncTool syncTool = 
tableContext.hiveSyncContext.hiveSyncTool()) {
+                            syncTool.syncHoodieTable();
+                            LOG.info("Successfully synced Hive metadata for 
table [{}]", tableId);
+                        }
+                    },
+                    "sync hive metadata for table %s",
+                    tableId);
+        }
+    }
+
+    @Nullable
+    private HiveSyncContext createHiveSyncContextIfNeeded(
+            Configuration tableConfig, TableState tableState) {
+        if (!tableState.syncHive) {
+            return null;
+        }
+        StorageConfiguration<org.apache.hadoop.conf.Configuration> storageConf 
=
+                
HadoopFSUtils.getStorageConfWithCopy(HadoopConfigurations.getHiveConf(tableConfig));
+        return HiveSyncContext.create(tableConfig, storageConf);
+    }
+
     @Override
     public void close() throws Exception {
         if (instantRequestExecutor != null) {
             instantRequestExecutor.close();
         }
+        if (hiveSyncExecutor != null) {
+            hiveSyncExecutor.close();
+        }
         tableContexts.values().forEach(TableContext::close);
         tableContexts.clear();
         pathToTableId.clear();
@@ -956,7 +1028,18 @@ public class MultiTableStreamWriteOperatorCoordinator 
extends StreamWriteOperato
         Schema cdcSchema =
                 Preconditions.checkNotNull(
                         tableSchemas.get(tableId), "Schema for " + tableId + 
"should not be null.");
-        return ConfigUtils.createTableConfig(baseConfig, cdcSchema, tableId);
+        Configuration tableConfig = ConfigUtils.createTableConfig(baseConfig, 
cdcSchema, tableId);
+        applyHiveSyncTableDefaults(tableConfig, tableId);
+        return tableConfig;
+    }
+
+    static void applyHiveSyncTableDefaults(Configuration tableConfig, TableId 
tableId) {
+        if (!tableConfig.contains(FlinkOptions.HIVE_SYNC_DB)) {
+            tableConfig.set(FlinkOptions.HIVE_SYNC_DB, 
tableId.getSchemaName());
+        }
+        if (!tableConfig.contains(FlinkOptions.HIVE_SYNC_TABLE)) {
+            tableConfig.set(FlinkOptions.HIVE_SYNC_TABLE, 
tableId.getTableName());
+        }
     }
 
     /** Provider for {@link MultiTableStreamWriteOperatorCoordinator}. */
diff --git 
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-hudi/src/test/java/org/apache/flink/cdc/connectors/hudi/sink/coordinator/MultiTableStreamWriteOperatorCoordinatorTest.java
 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-hudi/src/test/java/org/apache/flink/cdc/connectors/hudi/sink/coordinator/MultiTableStreamWriteOperatorCoordinatorTest.java
new file mode 100644
index 000000000..7df35d669
--- /dev/null
+++ 
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-hudi/src/test/java/org/apache/flink/cdc/connectors/hudi/sink/coordinator/MultiTableStreamWriteOperatorCoordinatorTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.cdc.connectors.hudi.sink.coordinator;
+
+import org.apache.flink.cdc.common.event.TableId;
+import org.apache.flink.configuration.Configuration;
+
+import org.apache.hudi.configuration.FlinkOptions;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link MultiTableStreamWriteOperatorCoordinator}. */
+class MultiTableStreamWriteOperatorCoordinatorTest {
+
+    @Test
+    void testApplyHiveSyncTableDefaultsDerivesFromTableIdWhenUnset() {
+        Configuration tableConfig = new Configuration();
+        TableId tableId = TableId.parse("my_db.my_table");
+
+        
MultiTableStreamWriteOperatorCoordinator.applyHiveSyncTableDefaults(tableConfig,
 tableId);
+
+        
assertThat(tableConfig.get(FlinkOptions.HIVE_SYNC_DB)).isEqualTo(tableId.getSchemaName());
+        
assertThat(tableConfig.get(FlinkOptions.HIVE_SYNC_TABLE)).isEqualTo(tableId.getTableName());
+    }
+
+    @Test
+    void testApplyHiveSyncTableDefaultsPreservesExplicitUserValues() {
+        Configuration tableConfig = new Configuration();
+        tableConfig.setString(FlinkOptions.HIVE_SYNC_DB.key(), "custom_db");
+        tableConfig.setString(FlinkOptions.HIVE_SYNC_TABLE.key(), 
"custom_table");
+        TableId tableId = TableId.parse("my_db.my_table");
+
+        
MultiTableStreamWriteOperatorCoordinator.applyHiveSyncTableDefaults(tableConfig,
 tableId);
+
+        
assertThat(tableConfig.get(FlinkOptions.HIVE_SYNC_DB)).isEqualTo("custom_db");
+        
assertThat(tableConfig.get(FlinkOptions.HIVE_SYNC_TABLE)).isEqualTo("custom_table");
+    }
+
+    @Test
+    void testApplyHiveSyncTableDefaultsHandlesPartiallySetConfig() {
+        Configuration tableConfig = new Configuration();
+        tableConfig.setString(FlinkOptions.HIVE_SYNC_DB.key(), "custom_db");
+        TableId tableId = TableId.parse("my_db.my_table");
+
+        
MultiTableStreamWriteOperatorCoordinator.applyHiveSyncTableDefaults(tableConfig,
 tableId);
+
+        
assertThat(tableConfig.get(FlinkOptions.HIVE_SYNC_DB)).isEqualTo("custom_db");
+        
assertThat(tableConfig.get(FlinkOptions.HIVE_SYNC_TABLE)).isEqualTo(tableId.getTableName());
+    }
+}

Reply via email to