This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 71b815d6ff [spark] Stabilize row tracking concurrent tests (#8307)
71b815d6ff is described below
commit 71b815d6ff833def99b62818ed9790df1ed5cfdb
Author: QuakeWang <[email protected]>
AuthorDate: Mon Jun 22 18:07:59 2026 +0800
[spark] Stabilize row tracking concurrent tests (#8307)
Row tracking concurrent tests could fail on transient commit conflicts
and snapshot reads while merge and compaction run in parallel. The
previous retry logic was duplicated and, in one path, retried without a
bound.
This PR centralizes the retry helper for these concurrent tests, adds a
bounded retry with a small backoff, preserves the final exception on
failure, and narrows snapshot missing detection to require `Snapshot
file` and `does not exist` in the same throwable message.
---
.../paimon/spark/sql/RowTrackingTestBase.scala | 137 ++++++++++++---------
1 file changed, 76 insertions(+), 61 deletions(-)
diff --git
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/RowTrackingTestBase.scala
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/RowTrackingTestBase.scala
index 5efcaf79b8..f53089470d 100644
---
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/RowTrackingTestBase.scala
+++
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/RowTrackingTestBase.scala
@@ -44,6 +44,43 @@ abstract class RowTrackingTestBase extends
PaimonSparkTestBase with AdaptiveSpar
import testImplicits._
+ private val MaxRetryAttempts = 20
+ private val RetryIntervalMillis = 10L
+
+ private def doWithRetry(doAction: () => Unit, retryableMessages: String*):
Unit = {
+ var attempts = 0
+ while (true) {
+ try {
+ doAction.apply()
+ return
+ } catch {
+ case e: Exception =>
+ attempts += 1
+ if (!isRetryable(e, retryableMessages) || attempts >=
MaxRetryAttempts) {
+ throw e
+ }
+ Thread.sleep(RetryIntervalMillis)
+ }
+ }
+ }
+
+ private def isRetryable(e: Throwable, retryableMessages: Seq[String]):
Boolean = {
+ hasMessage(e, "Snapshot file", "does not exist") ||
+ retryableMessages.exists(message => hasMessage(e, message))
+ }
+
+ private def hasMessage(e: Throwable, fragments: String*): Boolean = {
+ var current = e
+ while (current != null) {
+ val message = current.getMessage
+ if (message != null && fragments.forall(message.contains)) {
+ return true
+ }
+ current = current.getCause
+ }
+ false
+ }
+
test("Data Evolution: concurrent merge and compact") {
withTable("s", "t") {
sql(s"""
@@ -104,27 +141,16 @@ abstract class RowTrackingTestBase extends
PaimonSparkTestBase with AdaptiveSpar
Seq((1, 1, 1)).toDF("id", "b", "c").createOrReplaceTempView("s")
def doMerge(): Unit = {
- var success = false
- while (!success) {
- try {
- sql(s"""
- |MERGE INTO t
- |USING s
- |ON t.id = s.id
- |WHEN MATCHED THEN
- |UPDATE SET t.id = s.id, t.b = s.b + t.b, t.c = s.c + t.c
- |""".stripMargin).collect()
- success = true
- } catch {
- case e: Exception =>
- if (
- !e.getMessage.contains(
- "multiple 'MERGE INTO' operations have encountered
conflicts")
- ) {
- throw e
- }
- }
- }
+ doWithRetry(
+ () => sql(s"""
+ |MERGE INTO t
+ |USING s
+ |ON t.id = s.id
+ |WHEN MATCHED THEN
+ |UPDATE SET t.id = s.id, t.b = s.b + t.b, t.c = s.c +
t.c
+ |""".stripMargin).collect(),
+ "multiple 'MERGE INTO' operations have encountered conflicts"
+ )
}
val mergeInto1 = Future {
@@ -159,25 +185,25 @@ abstract class RowTrackingTestBase extends
PaimonSparkTestBase with AdaptiveSpar
val mergeB = Future {
for (_ <- 1 to 10) {
- sql(s"""
- |MERGE INTO t
- |USING sb
- |ON t.id = sb.id
- |WHEN MATCHED THEN
- |UPDATE SET t.b = sb.b + t.b
- |""".stripMargin).collect()
+ doWithRetry(() => sql(s"""
+ |MERGE INTO t
+ |USING sb
+ |ON t.id = sb.id
+ |WHEN MATCHED THEN
+ |UPDATE SET t.b = sb.b + t.b
+ |""".stripMargin).collect())
}
}
val mergeC = Future {
for (_ <- 1 to 10) {
- sql(s"""
- |MERGE INTO t
- |USING sc
- |ON t.id = sc.id
- |WHEN MATCHED THEN
- |UPDATE SET t.c = sc.c + t.c
- |""".stripMargin).collect()
+ doWithRetry(() => sql(s"""
+ |MERGE INTO t
+ |USING sc
+ |ON t.id = sc.id
+ |WHEN MATCHED THEN
+ |UPDATE SET t.c = sc.c + t.c
+ |""".stripMargin).collect())
}
}
@@ -199,33 +225,19 @@ abstract class RowTrackingTestBase extends
PaimonSparkTestBase with AdaptiveSpar
sql("INSERT INTO t VALUES (1, 0, 0)")
Seq((1, 1, 1)).toDF("id", "b", "c").createOrReplaceTempView("s")
- def doWithRetry(doAction: () => Unit): Unit = {
- var success = false
- while (!success) {
- try {
- doAction.apply()
- success = true
- } catch {
- case e: Exception =>
- if (
- !e.getMessage.contains("multiple 'MERGE INTO' and 'COMPACT'
operations")
- && !e.getMessage.contains("Row ID existence conflict")
- ) {
- throw e
- }
- }
- }
- }
-
val mergeInto = Future {
for (i <- 1 to 10) {
- doWithRetry(() => sql(s"""
- |MERGE INTO t
- |USING s
- |ON t.id = s.id
- |WHEN MATCHED THEN
- |UPDATE SET t.id = s.id, t.b = s.b + t.b,
t.c = s.c + t.c
- |""".stripMargin).collect())
+ doWithRetry(
+ () => sql(s"""
+ |MERGE INTO t
+ |USING s
+ |ON t.id = s.id
+ |WHEN MATCHED THEN
+ |UPDATE SET t.id = s.id, t.b = s.b + t.b, t.c = s.c +
t.c
+ |""".stripMargin).collect(),
+ "multiple 'MERGE INTO' and 'COMPACT' operations",
+ "Row ID existence conflict"
+ )
if (i > 1) {
sql(s"INSERT INTO t VALUES ($i, $i, $i)")
}
@@ -244,7 +256,10 @@ abstract class RowTrackingTestBase extends
PaimonSparkTestBase with AdaptiveSpar
while (!canBeCompacted) {
Thread.sleep(1)
}
- doWithRetry(() => sql("CALL sys.compact(table => 't')"))
+ doWithRetry(
+ () => sql("CALL sys.compact(table => 't')"),
+ "multiple 'MERGE INTO' and 'COMPACT' operations",
+ "Row ID existence conflict")
}
}