danny0405 commented on code in PR #20415:
URL: https://github.com/apache/hudi/pull/20415#discussion_r4373969334


##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieStreamingSink.scala:
##########
@@ -288,20 +294,157 @@ class HoodieStreamingSink(sqlContext: SQLContext,
     }
   }
 
-  private def reset(force: Boolean): Unit = this.synchronized {
+  /**
+   * Creates the write client for this sink with the sink's embedded timeline 
server, starting the server on first
+   * use. The client does not own the server, so the server keeps running for 
as long as the sink does.
+   */
+  private def createWriteClient(context: HoodieSparkEngineContext, 
writeConfig: HoodieWriteConfig): SparkRDDWriteClient[_] = {
+    // register the release first, so that a timeline server started for a 
client that then fails to build is
+    // still stopped when the query terminates
+    registerQueryTerminationListener()
+    val clientSchema = WriteClientSchema.of(writeConfig)
+    val client = new SparkRDDWriteClient(context, writeConfig, 
getOrStartTimelineService(context, writeConfig))
+    val previousClient = writeClient
+    // track the client as soon as it exists so that a failed micro-batch 
still reuses and eventually closes it
+    writeClient = Some(client)
+    writeClientSchema = Some(clientSchema)
+    // the writer re-creates the client when the write schema changes (as 
HoodieStreamer does): hand the new client
+    // to the async table services and close the previous one. Its timeline 
server is the sink's and keeps running.
+    previousClient.foreach(retireWriteClient(_, client))
+    client
+  }
+
+  /**
+   * Same as HoodieStreamer on a schema change (StreamSync#reInitWriteClient 
and
+   * HoodieStreamer#onInitializingWriteClient): the async table services 
switch to the new client for their next
+   * runs, and the previous client is closed right away.
+   */
+  private def retireWriteClient(previousClient: SparkRDDWriteClient[_], 
newClient: SparkRDDWriteClient[_]): Unit = {
+    if (asyncCompactorService != null) {
+      asyncCompactorService.updateWriteClient(newClient)
+    }
+    if (asyncClusteringService != null) {
+      asyncClusteringService.updateWriteClient(newClient)
+    }
+    closeQuietly(previousClient)

Review Comment:
   [P2] Wait for in-flight table services before closing their client
   
   This fixes retention by closing a client that can still be in use. 
HoodieSparkCompactor.compact captures the current client in a local variable 
and uses it for both compact and commitCompaction; HoodieSparkClusteringClient 
similarly captures its client for the run. updateWriteClient only changes the 
client for subsequent runs. Meanwhile close() stops heartbeats, closes 
transaction/index/table-service resources, and resets the old config's 
remote-view settings underneath the active run. A schema change during 
compaction/clustering can therefore terminate an otherwise healthy query; 
keeping the shared timeline server alive does not preserve these other 
resources. I saw the documented HoodieStreamer trade-off, but matching that 
behavior introduces the race into this sink and does not make a live-client 
close safe. Could we drain the current runs before closing, or use a bounded 
per-run lease? A latch-controlled test that pauses an async run, evolves the 
schema, and verifies the old c
 lient stays open through completion would cover this.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to