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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/scan/PluginDrivenScanNode.java:
##########
@@ -1898,6 +1911,9 @@ private void startStreamingSplit() {
                         LOG.warn("Failed to close streaming split source for 
{}", handle, ce);
                     }
                 }
+                List<ConnectorScanProfile> scanProfiles = 
onPluginClassLoader(scanProvider,

Review Comment:
   [P1] Drain the reporter before signaling split completion
   
   Both `finishSchedule()` and `setException()` notify split consumers before 
this `finally` closes the source and drains its report. There is consequently 
no happens-before edge from metric insertion to query/profile completion: the 
producer can be descheduled after the terminal signal and the final profile can 
be pushed without these metrics. Cancellation is worse because 
`SplitAssignment.stop()` neither closes this local source nor awaits the 
unretained future. Make close + collection part of the producer completion 
barrier before publishing success/error, and make stop own/close the active 
source while preserving the original scan error.



##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java:
##########
@@ -484,6 +484,12 @@ public ConnectorSplitSource streamSplits(ConnectorSession 
session, ConnectorTabl
         IcebergTableHandle iceHandle = (IcebergTableHandle) handle;
         Table table = resolveTable(session, iceHandle);
         TableScan scan = buildScan(table, iceHandle, filter, session);
+        // Match the eager planScan path: planFiles() emits its ScanReport 
when the streaming source closes.
+        // The engine drains the queryId-keyed profile after closing that 
source, so batch and non-batch scans
+        // expose the same Iceberg scan metrics without relying on a 
thread-local query context here.
+        if (session != null) {
+            scan = scan.metricsReporter(new 
IcebergScanProfileReporter(session.getQueryId(), scanProfileStash));

Review Comment:
   [P1] Preserve metrics on the manifest-cache streaming path
   
   When `meta.cache.iceberg.manifest.enable=true`, `streamingFileScanTasks` 
returns the custom `cacheBackedFileScanTasks` iterable. That iterator reads 
manifests and builds `BaseFileScanTask`s directly; neither it nor its `close()` 
ever calls `scan.planFiles()`, which is the operation that fires this attached 
reporter. The scan therefore returns all splits but `collectScanProfiles()` 
drains nothing, so the release-note behavior still disappears for a supported 
batch path. Please publish equivalent metrics for the cache-backed enumeration 
(including its fallback/error semantics) and cover a cache-enabled stream with 
an exactly-one-profile assertion.



##########
fe/fe-core/src/test/java/org/apache/doris/datasource/scan/PluginDrivenScanNodeScanProfileTest.java:
##########
@@ -87,4 +90,22 @@ public void sharesGroupAcrossScans() {
         Assertions.assertEquals("3", group.getChildMap().get("Table Scan 
(db.a)").getInfoString("data_files"));
         Assertions.assertEquals("5", group.getChildMap().get("Table Scan 
(db.b)").getInfoString("data_files"));
     }
+
+    @Test
+    public void concurrentStreamingScansShareOneGroup() {
+        RuntimeProfile summary = new RuntimeProfile("Execution Summary");
+        List<CompletableFuture<Void>> writes = new ArrayList<>();
+        for (int i = 0; i < 32; i++) {
+            String label = "Table Scan (db.t" + i + ")";

Review Comment:
   [P2] Make the concurrency regression deterministic
   
   This test does not force the pre-fix interleaving. The 32 common-pool tasks 
have no ready/start barrier or test seam that makes two writers both observe 
the group as absent before either adds it; if the pool has one worker, or the 
first short task completes lookup/add before the next lookup, the old racy 
implementation also reports 32 children and the test passes. Please use a 
controlled latch/barrier or an instrumented lookup/add seam so the test 
reliably fails without the new synchronization.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/scan/PluginDrivenScanNode.java:
##########
@@ -416,11 +416,7 @@ static void writeScanProfilesInto(RuntimeProfile 
executionSummary, List<Connecto
             return;
         }
         for (ConnectorScanProfile profile : profiles) {
-            RuntimeProfile group = 
executionSummary.getChildMap().get(profile.getGroupName());
-            if (group == null) {
-                group = new RuntimeProfile(profile.getGroupName());
-                executionSummary.addChild(group, true);
-            }
+            RuntimeProfile group = 
getOrCreateScanProfileGroup(executionSummary, profile.getGroupName());

Review Comment:
   [P1] Keep profiles from repeated scans of the same table
   
   Serializing group creation does not prevent loss when two scan nodes have 
the same child name. Iceberg derives `scanLabel` only from 
`scanReport.tableName()`, so a self-join or two `UNION ALL` branches over the 
same table both produce `Table Scan (db.t)`. `RuntimeProfile.addChild` then 
removes and replaces the existing same-named child, leaving only one branch's 
metrics. Please give each scan occurrence a stable unique identity (or 
otherwise preserve duplicate labels), and add a same-label 
sequential/concurrent test rather than only `t0` through `t31`.



##########
fe/fe-connector/fe-connector-spi/src/main/java/org/apache/doris/connector/spi/scan/ConnectorScanPlanProvider.java:
##########
@@ -282,12 +282,13 @@ default OptionalLong 
scannedPartitionCount(List<ConnectorScanRange> scanRanges)
      * {@link ConnectorScanProfile} groups the engine writes into the query's 
profile execution summary.
      *
      * <p>The default returns an empty list (connector reports nothing). A 
connector that wants scan
-     * diagnostics harvests them from its SDK during {@code planScan} (the 
paimon SDK exposes a metric
-     * registry, the iceberg SDK a metrics reporter), stashes them keyed by 
{@link ConnectorSession#getQueryId()},
-     * and drains them here — mirroring the per-query queryId stashes this SPI 
already uses (read-transaction
-     * release, rewritable-delete supply). The engine calls this immediately 
after {@code planScan} on the
-     * same thread, so the harvest is complete; the connector must also drop 
its stash on
-     * {@link #releaseReadTransaction} to reclaim any entry a thrown {@code 
planScan} left behind.</p>
+     * diagnostics harvests them from its SDK during {@code planScan} or 
streaming split generation (the paimon
+     * SDK exposes a metric registry, the iceberg SDK a metrics reporter), 
stashes them keyed by
+     * {@link ConnectorSession#getQueryId()}, and drains them here — mirroring 
the per-query queryId stashes this
+     * SPI already uses (read-transaction release, rewritable-delete supply). 
The engine calls this immediately
+     * after {@code planScan}, or after closing a streaming split source, so 
the harvest is complete; the connector

Review Comment:
   [P1] Preserve the API-4 collection lifecycle
   
   Before this change, this public SPI contract guaranteed that 
`collectScanProfiles` was called immediately after `planScan` on that same 
thread. FE now also calls an existing override after `streamSplits` on the 
schedule executor, on a path that never called `planScan`. An API-4 external 
connector may implement streaming and eager diagnostics while validly depending 
on prior `planScan` state or planning-thread context; the unchanged version 
gate will still load it and FE can now call it in an unsupported lifecycle. 
Please add a separate opt-in streaming-profile hook with a safe default, or 
treat the lifecycle/threading expansion as an incompatible API revision and 
bump/adapt the plugin contract.



##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java:
##########
@@ -484,6 +484,12 @@ public ConnectorSplitSource streamSplits(ConnectorSession 
session, ConnectorTabl
         IcebergTableHandle iceHandle = (IcebergTableHandle) handle;
         Table table = resolveTable(session, iceHandle);
         TableScan scan = buildScan(table, iceHandle, filter, session);
+        // Match the eager planScan path: planFiles() emits its ScanReport 
when the streaming source closes.
+        // The engine drains the queryId-keyed profile after closing that 
source, so batch and non-batch scans
+        // expose the same Iceberg scan metrics without relying on a 
thread-local query context here.
+        if (session != null) {
+            scan = scan.metricsReporter(new 
IcebergScanProfileReporter(session.getQueryId(), scanProfileStash));

Review Comment:
   [P1] Avoid the shared formatter on concurrent report callbacks
   
   This makes `IcebergScanProfileReporter.report()` reachable concurrently from 
separate streaming scan-node tasks, but every reporter in the plugin 
classloader shares the static `DecimalFormat BYTES_FORMAT`. 
`NumberFormat`/`DecimalFormat` require separate instances or external 
synchronization, so concurrent byte-counter rendering can corrupt profile 
strings or throw while the source is closing. Use per-call or thread-local 
formatting and add a parallel reporter test; the new concurrency test exercises 
only RuntimeProfile insertion.



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