github-actions[bot] commented on code in PR #65553:
URL: https://github.com/apache/doris/pull/65553#discussion_r3575825419


##########
fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java:
##########
@@ -1841,7 +1841,7 @@ public void abortTransaction(Long dbId, Long 
transactionId, String reason,
 
         AbortTxnResponse abortTxnResponse = null;
         try {
-            abortTxnResponse = abortTransactionImpl(dbId, transactionId, 
reason, null);
+            abortTxnResponse = abortTransactionImpl(dbId, transactionId, 
reason, txnCommitAttachment);

Review Comment:
   This fixes the OK cloud abort path, but the idempotent retry path still 
loses the rollback attachment. If meta-service already processed the abort and 
a retry returns `TXN_ALREADY_ABORTED`, `abortTransactionImpl()` calls 
`afterAbortTxnResp()` before returning, so this assignment is skipped and the 
`finally` calls `handleAfterAbort(null, ...)`. That callback sees an empty, 
non-operated `TransactionState`, so `RoutineLoadJob.afterAborted()` never 
records the `firstErrorMsg`, even though meta-service returns the aborted 
`txn_info` with the request attachment copied into it. Please treat 
`TXN_ALREADY_ABORTED` with returned txn info as an idempotent abort for the 
callback, or otherwise pass the returned/requested attachment into 
`handleAfterAbort()`, and add a cloud abort retry test.



##########
regression-test/suites/load_p0/routine_load/test_routine_load_first_error_msg.groovy:
##########
@@ -0,0 +1,127 @@
+// 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.
+
+import org.apache.doris.regression.util.RoutineLoadTestUtils
+import org.apache.kafka.clients.admin.AdminClient
+import org.apache.kafka.clients.admin.NewTopic
+import org.apache.kafka.clients.producer.ProducerConfig
+import org.junit.Assert
+
+suite("test_routine_load_first_error_msg", "p0") {
+    if (!RoutineLoadTestUtils.isKafkaTestEnabled(context)) {
+        return
+    }
+
+    def kafkaBroker = RoutineLoadTestUtils.getKafkaBroker(context)
+    def kafkaTopic = 
"test_routine_load_first_error_msg_${System.currentTimeMillis()}"
+    def jobName = "test_routine_load_first_error_msg"
+
+    def adminProps = new Properties()
+    adminProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaBroker)
+    def adminClient = AdminClient.create(adminProps)
+    try {
+        adminClient.createTopics([new NewTopic(kafkaTopic, 1, (short) 
1)]).all().get()
+    } finally {
+        adminClient.close()
+    }
+
+    try {
+        sql "DROP TABLE IF EXISTS test_routine_load_first_error_msg"
+        sql """
+            CREATE TABLE test_routine_load_first_error_msg (
+                id INT,
+                name STRING
+            )
+            PARTITION BY RANGE(id) (
+                PARTITION p0 VALUES LESS THAN ("10")
+            )
+            DISTRIBUTED BY HASH(id) BUCKETS 1
+            PROPERTIES ("replication_num" = "1")
+        """
+
+        sql """
+            CREATE ROUTINE LOAD ${jobName} ON test_routine_load_first_error_msg
+            COLUMNS TERMINATED BY "|"
+            PROPERTIES (
+                "max_error_number" = "10",
+                "max_filter_ratio" = "1.0",
+                "max_batch_interval" = "5"
+            )
+            FROM KAFKA (
+                "kafka_broker_list" = "${kafkaBroker}",
+                "kafka_topic" = "${kafkaTopic}",
+                "property.kafka_default_offsets" = "OFFSET_END"
+            )
+        """
+
+        def count = 0
+        while (true) {
+            def showResult = sql "SHOW ROUTINE LOAD FOR ${jobName}"
+            if (showResult[0][8].toString() == "RUNNING") {
+                break
+            }
+            if (count++ > 60) {
+                Assert.fail("Routine Load job did not enter RUNNING state")
+            }
+            sleep(1000)
+        }
+
+        def producer = RoutineLoadTestUtils.createKafkaProducer(kafkaBroker)
+        try {
+            RoutineLoadTestUtils.sendTestDataToKafka(
+                    producer, [kafkaTopic], ["100|bad_row", "1|valid_row"])
+            producer.flush()
+        } finally {
+            producer.close()
+        }
+
+        count = 0
+        while (true) {
+            def jobInfo = sql """

Review Comment:
   This query can pick up a stopped job from a previous run. The suite uses the 
fixed routine-load name and only stops the job in `finally`; stopped jobs 
remain until `label_keep_max_second` expires, while `isNameUsed()` ignores 
final-state jobs so a rerun can create another job with the same name. 
`information_schema.routine_load_jobs` is populated from 
`getAllRoutineLoadJobs()` without filtering to the active job, and the test 
reads `jobInfo[0]`, so it can compare the old row's URL/message against the new 
`SHOW ROUTINE LOAD` result or time out. Please use a unique job name or filter 
by the current job id/state before reading the information_schema row.



##########
be/src/load/stream_load/stream_load_executor.cpp:
##########
@@ -428,6 +430,9 @@ bool 
StreamLoadExecutor::collect_load_stat(StreamLoadContext* ctx, TTxnCommitAtt
         rl_attach.__set_receivedBytes(ctx->receive_bytes);
         rl_attach.__set_loadedBytes(ctx->loaded_bytes);
         rl_attach.__set_loadCostMs(ctx->load_cost_millis);

Review Comment:
   `RuntimeState::append_error_msg_to_file()` records the first message as the 
full error plus source line before the error-log-file byte cap, and this line 
sends that raw string inside `TLoadTxnCommitRequest` / 
`TLoadTxnRollbackRequest`. The new FE-side abbreviation in 
`RLTaskTxnCommitAttachment` only runs after the Thrift request has already been 
received, so one large malformed Kafka row can still make the routine-load 
transaction RPC carry a very large payload or exceed FE thrift frame/message 
limits before the durable/cloud cap protects anything. Please cap 
`ctx->first_error_msg` before setting 
`TRLTaskTxnCommitAttachment.firstErrorMsg` or otherwise at the BE RPC 
serialization boundary, and cover an overlong routine-load bad row.



##########
fe/fe-core/src/main/java/org/apache/doris/load/routineload/RoutineLoadJob.java:
##########
@@ -1712,6 +1724,7 @@ public List<String> getShowInfo() {
             row.add(userIdentity.getQualifiedUser());
             row.add(comment);
             row.add(getClusterInfo());
+            row.add(getFirstErrorMsg());
             return row;
         } finally {
             readUnlock();

Review Comment:
   This timestamps the diagnostic when FE processes the attachment, but the URL 
lifetime is controlled by BE error-log file age. A routine-load job can legally 
set `max_batch_interval` above the default 48-hour BE 
`load_error_log_reserve_hours`; if the first bad row appears early and the 
transaction callback arrives after BE cleanup, FE records a fresh timestamp 
here and keeps showing a dead URL/message for another retention window. Replay 
has the same problem because old attachments are repopulated with the replay 
time. Please timestamp this from a value tied to the BE log lifetime, such as 
task/error-log creation time or transaction finish time, and skip or expire 
entries whose backing log may already be gone.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to