morningman opened a new pull request, #68901:
URL: https://github.com/apache/doris/pull/68901

   ### What problem does this PR solve?
   
   Issue Number: None
   
   Related PR: #66473, #66530
   
   Problem Summary:
   
   **In short.** With `enable_external_scan_task_reuse` on (the default), the 
Hive, Hudi, Iceberg and Paimon connectors keep the planned range list of every 
scan in the statement scope until the statement ends, even when no other scan 
of the statement can reuse it. Reuse can only happen while the statement is 
being planned, so this PR drops the memo when `NereidsPlanner.plan()` returns 
or throws. branch-4.x (#66473) bounds the same reuse with per-statement budgets 
instead; master had no bound.
   
   **Background.** #66530 lets the identical scans of one statement (a self 
join, a UNION of the same table, a CTE read twice) plan once: each of the four 
scan providers memoizes the final `List<ConnectorScanRange>` of a synchronous 
`planScan` in a map hung in the statement's `ConnectorStatementScope`, keyed by 
everything the ranges depend on. The scope lives until the statement's 
query-finish callback closes it. Without the memo the ranges are garbage as 
soon as the scan node has turned them into `TScanRangeLocations`, because the 
scan node keeps no reference to them.
   
   **The problem, and what it cost.** The memo is the only long-lived reference 
to the ranges, and it keeps them for the whole execution:
   
   - A statement with a single relation, which can never reuse anything, still 
keeps all of its ranges until it ends.
   - Paimon and Hudi have no batch path, so every scan of them keeps its whole 
range list; a Paimon JNI range carries the Base64-serialized `DataSplit`, which 
grows with the number of files. Hive keeps it for an unpartitioned table or 
below the batch threshold, and Iceberg until a scan switches to streaming.
   - An Arrow Flight statement keeps it until the client has fetched the whole 
result.
   
   Reuse needs none of this. Scans are planned only inside 
`NereidsPlanner.plan()` (translation finalizes each scan node, which calls 
`planScan`); the coordinator, Arrow Flight's DoGet and `handleQueryWithRetry` 
never plan a scan again, and the batch / streaming paths that produce splits 
during execution do not use the memo. A statement planned more than once (cloud 
replan, INSERT retry, the probe of INSERT OVERWRITE) gets a new query id or a 
reset scope each time, so no plan reuses the memo of another plan today either.
   
   **How this PR fixes it.**
   
   - `ConnectorStatementScope` (SPI) gains `computeIfAbsentForPlanning`, for a 
value only planning reads, and `endPlanning`, which drops those values. The 
defaults keep today's behavior (`computeIfAbsent` and a no-op), and `NONE` 
still memoizes nothing. A value registered for planning must hold nothing to 
close, since it is dropped without being closed. The connector SPI has not 
shipped in a release, so `connector.plugin.api.version` stays as it is; 
`ConnectorStatementScope` is not part of the frozen plugin surface baselines.
   - `ConnectorStatementScopeImpl` records the keys registered for planning, 
and `endPlanning` removes them; the other values of the statement (metadata, 
tables, transactions) stay until `closeAll`.
   - `NereidsPlanner.plan` ends connector planning in its `finally` block, 
through `StatementContext.endConnectorPlanning`, which never creates a scope 
for a statement no connector has touched.
   - The Hive, Hudi, Iceberg and Paimon providers register their range memo for 
planning. The Hudi and Paimon table-resolution maps, which 
`getScanNodeProperties` also reads, stay for the statement. Fluss reads its 
lake table through the Paimon provider, so it follows.
   
   A scan planned during execution through the default 
`planScanForPartitionBatch` (only MaxCompute uses it, and MaxCompute keeps no 
memo) would register the memo again and keep it until the statement ends, as 
today; the SPI javadoc says so.
   
   **Results.**
   
   | Statement | Before: the planned ranges are held until | After |
   |---|---|---|
   | SELECT, INSERT ... SELECT | the statement ends | `plan()` returns |
   | Arrow Flight query | the client has fetched the result | `plan()` returns |
   | EXPLAIN | the statement ends | `plan()` returns |
   | Planning fails | the statement ends | `plan()` throws |
   
   Identical scans of one plan still share their ranges. On a local cluster 
with a filesystem Paimon catalog, a self join, a UNION ALL of the same table, a 
self join filtered on a partition and a repeated statement return the expected 
rows, and EXPLAIN shows both scans of the self join with the same splits.
   
   ### Release note
   
   None
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [ ] Regression test
       - [x] Unit Test
           - `ConnectorStatementScopeTest`: `endPlanning` drops only the 
planning values, and a later plan of the statement registers and drops them 
again; `NONE` never memoizes them; a planning value must hold nothing to close; 
`StatementContext.endConnectorPlanning` ends the planning of its scope.
           - `NereidsPlannerConnectorPlanningTest`: `plan()` ends connector 
planning for a statement it plans (`select 1`) and for one that fails analysis.
           - Hive, Hudi, Iceberg and Paimon 
`statementReuseLastsOnlyWhilePlanning`: once planning ends the memo is gone and 
an identical scan plans again; Paimon keeps the table resolved for the 
statement.
           - Mutations: not ending planning in `plan()`, an `endPlanning` that 
drops nothing, dropping the closeable check, or registering a provider's memo 
with `computeIfAbsent` each makes exactly the matching new cases fail.
           - The fe-core scope and planner tests (49), fe-connector-spi (146), 
Hive (492), Hudi (252), Iceberg (1500), Paimon (696) and Fluss (290) pass.
           - The branch-4.x budget tests 
(`testOversizedIcebergPlanIsNotRetained`, 
`testStatementCacheStopsSerializingEntryOverByteBudget`, 
`HudiScanNodeTest.testOversizedSnapshotPlanIsNotRetained` and the like) are not 
ported: there is no budget to test.
       - [x] Manual test (add detailed scripts or steps below)
           - A local FE + BE whose FE is this change on master `038e2274ef6`, 
with a filesystem Paimon catalog: `SELECT count(*) FROM t a JOIN t b ON a.id = 
b.id`, a `UNION ALL` of the same table, a self join filtered on a partition, 
and the first statement again return the expected rows; EXPLAIN shows both 
scans of the self join with `inputSplitNum=3`.
       - [ ] No need to test or manual test. Explain why:
           - [ ] This is a refactor/code format and no logic has been changed.
           - [ ] Previous test can cover this change.
           - [ ] No code files have been changed.
           - [ ] Other reason <!-- Add your reason?  -->
   
   - Behavior changed:
       - [x] No. Identical scans of one plan still share their ranges; the 
ranges are no longer held after planning.
       - [ ] Yes. <!-- Explain the behavior change -->
   
   - Does this need documentation?
       - [x] No.
       - [ ] Yes. <!-- Add document PR link here. eg: 
https://github.com/apache/doris-website/pull/1214 -->
   
   ### Check List (For Reviewer who merge this PR)
   
   - [ ] Confirm the release note
   - [ ] Confirm test cases
   - [ ] Confirm document
   - [ ] Add branch pick label <!-- Add branch pick label that this PR should 
merge into -->
   


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