wgzhao opened a new issue, #13108:
URL: https://github.com/apache/seatunnel/issues/13108

   ### Search before asking
   
   - [x] I had searched the issues and found no open issue with a root-cause 
analysis for this problem. Two closed reports describe the same symptom without 
a resolution: #7050 (2.3.5, multi-table CDC writing to one Doris table) and 
#9048 (2.3.9, `LABEL_ALREADY_EXISTS`). Both were closed as stale. This issue is 
not a duplicate: it identifies the label-generation path that causes the 
collision on SeaTunnel 3.0.0 and proposes a fix.
   
   ### What happened
   
   A MySQL-CDC streaming job reads 12 monthly shard tables with a 
`table-pattern` and writes all of them into a single Doris table. `parallelism 
= 1`, `sink.enable-2pc = false`, default `doris.batch.size = 1024`, checkpoint 
interval 5s.
   
   At a checkpoint boundary, two Doris sub-writers generated the same 
stream-load label. Doris rejected the second load with `LABEL_ALREADY_EXISTS`. 
The Doris sink writer treats that status as fatal, so the whole pipeline 
failed. Restoring from the savepoint replayed the same window and the failure 
repeated within seconds.
   
   The job had been running for days with only one active source shard. The 
failure started when a second shard table began receiving changes. The same 
label-generation logic exists in 2.3.13, so this is a latent defect rather than 
a 3.0.0-only regression; it is reported here on 3.0.0.
   
   ### SeaTunnel Version
   
   3.0.0 (Zeta engine, single node; Java 8).
   
   ### SeaTunnel Config
   
   ```hocon
   env {
     parallelism = 1
     job.mode = "STREAMING"
     checkpoint.interval = 5000
     read_limit.rows_per_second = 40000
   }
   
   source {
     MySQL-CDC {
       server-id = 5401
       username = "repl"
       password = "***"
       database-names = ["shop"]
       table-pattern = "shop\\.orders_2021.*"   # 12 monthly shard tables
       url = "jdbc:mysql://mysql.example.com:3306/?autoReconnect=true"
       schema-changes.enabled = false
       startup.mode = "latest"
     }
   }
   
   sink {
     Doris {
       fenodes = "doris-fe.example.com:8030"
       query-port = 19030
       username = "loader"
       password = "***"
       database = "analytics"
       table = "orders_all"                  # all 12 source tables write here
       schema_save_mode = "ignore"
       sink.enable-2pc = "false"
       sink.label-prefix = "orders_label"
       doris.config = {
         format = "json"
         read_json_by_line = "true"
       }
     }
   }
   ```
   
   ### Running Command
   
   ```shell
   bin/seatunnel.sh -c job/orders_cdc_demo.conf -n orders_cdc_demo -r 
1234567890123456789 --async
   ```
   
   ### Error Exception
   
   Anonymized excerpt (`T` = checkpoint completion time; the label suffix 
`1700000000452` is the millisecond timestamp at which both writers generated 
the label):
   
   ```log
   2026-01-01 00:00:10.452 INFO  [CheckpointCoordinator] - pending checkpoint 
notify finished, job id: 1234567890123456789, checkpoint id: 100
   
   # Two sub-writers generated the same label at 00:00:10.452:
   2026-01-01 00:00:10.540 INFO  [DorisStreamLoad] - stream load started for 
orders_label_analytics_orders_all_1234567890123456789_0_1700000000452
   2026-01-01 00:00:12.210 INFO  [DorisStreamLoad] - load Result {
       "TxnId": 100001,
       "Label": 
"orders_label_analytics_orders_all_1234567890123456789_0_1700000000452",
       "Status": "Success",
       "NumberTotalRows": 1024,
       ...
   }
   2026-01-01 00:00:12.568 INFO  [DorisStreamLoad] - stream load started for 
orders_label_analytics_orders_all_1234567890123456789_0_1700000000452
   2026-01-01 00:00:12.600 INFO  [DorisStreamLoad] - load Result {
       "TxnId": -1,
       "Label": 
"orders_label_analytics_orders_all_1234567890123456789_0_1700000000452",
       "Status": "Label Already Exists",
       "Message": "[LABEL_ALREADY_EXISTS]TStatus: errCode = 2, detailMessage = 
Label [...] has already been used, relate to txn [100001], status [VISIBLE].",
       ...
   }
   2026-01-01 00:00:12.610 ERROR [DorisSinkWriter] - stream load finished 
unexpectedly: [LABEL_ALREADY_EXISTS] ...
   2026-01-01 00:00:15.493 ERROR [SubPlan] - Task ... Failed in Job 
orders_cdc_demo (1234567890123456789), Pipeline: [(1/1)], Begin to cancel other 
tasks in this pipeline.
   ```
   
   The second load starts 2.5 s after the first because `DorisStreamLoad` only 
opens the HTTP load when the first record of the buffer arrives. The label 
itself was generated at 00:00:10.452 by both writers. On every restore, the 
engine also logs 12 `DorisSinkWriter - labelPrefix orders_label` lines within a 
few milliseconds, one per source table.
   
   ### Root cause
   
   Relevant code on `v3.0.0`:
   
   - 
[`DorisSinkWriter`](https://github.com/apache/seatunnel/blob/v3.0.0/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java#L121-L129)
 builds the label prefix from the configured prefix, the **sink** table path, 
the job id and `context.getIndexOfSubtask()`. In a multi-table sink where every 
source table maps to one Doris table, all sub-writers get the same prefix. 
`getIndexOfSubtask()` is the engine subtask index, not a per-sub-sink index.
   - 
[`LabelGenerator.generateLabel`](https://github.com/apache/seatunnel/blob/v3.0.0/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/LabelGenerator.java#L30-L34)
 uses `prefix + "_" + System.currentTimeMillis()` for non-2PC. Two sub-writers 
that call it in the same millisecond produce identical labels.
   - 
[`MultiTableSink.createWriter`](https://github.com/apache/seatunnel/blob/v3.0.0/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSink.java#L125-L143)
 creates one writer per source table.
   - At every checkpoint, 
[`MultiTableSinkWriter.snapshotState`](https://github.com/apache/seatunnel/blob/v3.0.0/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriter.java#L681-L721)
 iterates all sub-writers and each 
[`DorisSinkWriter.snapshotState`](https://github.com/apache/seatunnel/blob/v3.0.0/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java#L299-L304)
 calls `startLoad(generateLabel(...))`. This makes several label generations 
within the same millisecond likely.
   - 
[`DorisSinkWriter.flush`](https://github.com/apache/seatunnel/blob/v3.0.0/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java#L274-L286)
 and 
[`checkDone`](https://github.com/apache/seatunnel/blob/v3.0.0/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java#L326-L341)
 treat any status other than `Success`/`Publish Timeout` as fatal.
   
   Doris requires labels to be unique per database; a second load with a used 
label returns `LABEL_ALREADY_EXISTS`. The two loads carry different data 
(different source tables), so treating the second as success would silently 
drop data. The only correct fix is to generate unique labels.
   
   Why it is intermittent: the collision requires two sub-writers to generate a 
label in the same millisecond and both to start their load. With one active 
source table there is no collision. In the reported job, 2 of the 12 shard 
tables were active; one had far more changes than the other, and a single 
change on the second table was enough to trigger the failure at the next 
checkpoint.
   
   ### Reproduction
   
   1. Create two source tables under `shop` (for example `orders_202101`, 
`orders_202102`) and one Doris table `analytics.orders_all`.
   2. Run a MySQL-CDC job with `table-pattern = "shop\\.orders_2021.*"`, `table 
= "orders_all"`, `sink.enable-2pc = false`, the default batch size and a 5 s 
checkpoint interval.
   3. Write continuously to both source tables (or replay a binlog backlog) so 
both sub-writers flush at the same checkpoints. The job fails with 
`LABEL_ALREADY_EXISTS`.
   4. Deterministic micro-reproduction of the label generator: create two 
`LabelGenerator` instances with the same prefix and call `generateLabel(0)` 
from both in a tight loop; labels duplicate when calls land in the same 
millisecond. With a unique per-call suffix, 2,000,000 generated labels were 
unique in a local check.
   
   ### Expected behavior
   
   Every stream-load label generated by the sub-writers of one job must be 
unique, even when several sub-writers share the same sink table, job id and 
subtask index and generate labels in the same millisecond. A multi-table CDC 
job writing to a single Doris table must run without `LABEL_ALREADY_EXISTS`. 
2PC labels (`prefix + checkpointId`) are used by abort/recovery and should 
remain deterministic.
   
   ### Workaround
   
   Split the job into one job per source table so each job gets a different job 
id in the label prefix. This avoids the collision but multiplies jobs and 
MySQL-CDC server ids. It is not a fix.
   
   ### Related issues
   
   - #7050 (2.3.5): multi-table CDC to one Doris table, `Label already exists`; 
closed stale.
   - #9048 (2.3.9): `LABEL_ALREADY_EXISTS`; closed stale.
   
   ### Are you willing to submit PR?
   
   - [x] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct).
   


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