Repository navigation
[improvement](datasource) Bound and fairly schedule external scan planning - #69298
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
run buildall |
|
/review |
There was a problem hiding this comment.
Reviewed #69298 at 7de569daeb9521790075380d85a36e81968bf400 against the supplied authoritative aggregate diff. All 77 changed paths were covered by the main review and two reviewers with complementary scopes; an additional reviewer checked four cross-boundary risks.
The scheduling and scan hot-path changes broadly implement their stated goal. Four P2 issues are reported inline: retained manifests fail after source-schema cleanup, recovered schema names can collide with partition names, detached prefetch can outlive its FileIO owner, and one existing producer test retains malformed scan envelopes. No existing inline threads or independently confirmed existing P0/P1 comments were present. No additional user focus was supplied.
The invocation chain reviewed was:
statement -> captured query groups -> SPLIT producer -> Hive/Iceberg source
| |
| METADATA workers
v |
assignment + BE queues <-+
|
SplitSource / compaction
|
BE descriptor iterator -> per-file reader
finish/cancel -> stop groups/producers -> statement/read-lease cleanup
Applicable code-review skill checkpoints:
| Checkpoint | Conclusion |
|---|---|
| Goal and proof | Fair task groups, separate metadata workers, bounded completion-order prefetch and smaller transport envelopes are implemented. Checked-in tests cover their main behavior; the four reported cases need fixes. |
| Scope and abstraction | Broad but related changes span scheduling, lifetime and scan transport. Shared helpers and SPI keep generic planning connector-agnostic. |
| Concurrency and protected state | Worker, planner, cancellation and RPC threads use captured sessions, atomics/concurrent queues, group admission locks, producer scheduleLock and assignment/readiness/consumer locks. The detached-load ownership issue is reported inline. |
| Work under locks | External reads, future waits and user callbacks are outside registry locks; backend-policy mutation remains serialized separately from readiness publication. |
| Lock order and deadlock | Reviewed admission/listener/queue/cleanup paths showed no additional concrete lock cycle. |
| Lifecycle and initialization | Captured groups reject resurrection, queued work cancels, and source cleanup is cooperative. Running metadata callables still need ownership through actual exit. No C++ static-initialization change. |
| Configuration | Three FE worker/capacity settings are restart-scoped and captured at creation. The forwarded session manifest concurrency is validated to 1..64 and shared across query scans. |
| Symbol/protocol/storage compatibility | Connector API major advances 18 to 19 with surface/version baselines; no new persisted format. Existing BE readers support nested range descriptors. |
| Thrift/protobuf definitions | None changed. Existing optional per-range fields and query-option defaults were checked at consumption sites. |
| Parallel paths | Both Coordinator and ThriftPlansBuilder compact transport copies; streaming enforces the original split count before compaction. Native/JNI and row-id paths consume per-range formats. |
| Special conditions | Backpressure/cancellation conditions have concrete triggers and comments. URI-prefix reuse and backend candidate clamping preserve reviewed semantics. Dropped-source recovery has the two inline defects. |
| Test coverage | Positive/negative, cancellation, failure, queue-capacity, single-worker, race, serialization, path and schema-promotion cases exist. The stale producer fixture introduces an intermittent test failure. |
| Changed expected results | Hive fixture totals and Iceberg count/sum/union/limit outputs are consistent by inspection. Output-generation provenance was not independently reproduced. |
| Observability | Fair-executor pool/active/queue/group/completion metrics and existing generation/cache profiles and exception context cover the new paths. No separate proven gap found. |
| Transactions, persistence and failover | Read planning and write-format inference are changed; no EditLog, committed-data visibility, rowset version or MoW delete-bitmap protocol change. Read resource ownership was traced through statement cleanup. |
| Writes and atomicity | No new data-write or commit protocol. Write-side ORC inference remains; no additional atomicity defect identified. |
| FE-to-BE values | Formats, credentials, partitions and connector descriptors remain in existing fields and survive both transport paths. |
| Performance and memory | Producer work, task queues, prefetch results and query read permits are bounded. Warm-cache reads avoid futures; path reuse and envelope compaction avoid repeated wrapper work. Scanner concurrency is preserved by the wrapper floor. |
| Other issues and final recheck | Retry/Flight/planning-only cleanup, continuation admission, schema evolution/delete matching, mixed formats/JNI and test fixtures were rechecked. Every candidate and initial risk has an explicit ledger disposition. |
Validation was static only, as required: no builds, tests, cluster execution or source edits. Author-reported performance and test results were not independently rerun. There are no unresolved candidates or uncovered changed paths within this static scope. The review converged after 2 rounds: every assigned reviewer returned NO_NEW_VALUABLE_FINDINGS on the same frozen set.
Review converged after 2 round(s).
TPC-H: Total hot run time: 28431 ms |
TPC-DS: Total hot run time: 150261 ms |
ClickBench: Total hot run time: 24.35 s |
7de569d to
eadfc6f
Compare
|
run buildall |
|
/review |
There was a problem hiding this comment.
Static review of all 77 changed paths at eadfc6f identified one additional P2: an outer cloud replan retry can permanently retain newly allocated scheduler groups after repopulating a closed statement scope. The inline comment describes the trigger, ownership chain and fix direction.
The four existing P2 reports remain applicable and are not duplicated: retained-manifest schema cleanup (4317059423), recovery-schema name collisions (4317059437), metadata callable/table-owner lifetime (4317059444), and malformed producer-test envelopes (4317059450). There are no existing P0/P1 findings to carry as blocking IDs. No additional user-provided focus was supplied.
- Goal and proof: the code implements query-owned fair scheduling, separate metadata workers, cooperative split production, bounded completion-ordered prefetch and smaller transport envelopes. Tests cover these mechanisms; the new retry leak and known P2 cases qualify the correctness conclusion.
- Scope and clarity: the changes are broad but follow the documented scheduling/hot-path goal and connector SPI boundary. Per-file format replaces read-side format inference; writer inference remains available.
- Concurrency: traced planning, SPLIT/METADATA workers, BE-fetch RPCs and cancellation/finalization. CAS/queues own task publication; admission, producer, readiness, backend-policy and per-backend wait locks protect their respective state. Tasks, IO and callbacks execute outside scheduler registry locks. No additional deadlock, lost wakeup or concurrent source iteration was verified.
- Lifecycle: captured groups reject resurrection through an old session; normal cleanup, planning failure, inner retries and deferred Flight were traced. Outer replan scope reuse is the new inline finding; running FileIO owner retention is the existing report. No static initialization issue applies.
- Configuration: three FE thread/queue settings are nonmutable and captured at executor creation; no dynamic-resize promise is made. Session manifest concurrency is forwarded, checked in [1,64] and shared through a query-scoped semaphore.
- Compatibility: connector API advances 18.0 to 19.0 with its version assertion and plugin surface baseline. New SPI defaults/forwarding are present. ConnectorMetadata methods, persisted formats and existing wire descriptors are unchanged.
- Thrift/protobuf definitions: none changed; cross-branch field-ID comparison is inapplicable.
- Parallel paths: checked Hive/Iceberg, cache-on/off, synchronous/streaming/COUNT, metadata tables, both FE dispatch builders and BE local/remote split readers. Per-file formats precede reader/schema setup, including ORC and row-ID fetch.
- Special conditions: startup demand bypasses the single-lane case; terminal changes wake consumers; admission rechecks cover early dequeues. Partition adaptation uses field IDs, preserves nulls and rejects lossy narrowing. Known schema-recovery cases remain reported above.
- Test coverage: reviewed changed unit and regression cases for queue pressure, fairness, cancellation, failure, schema promotions, deletes, mixed formats, serialization and LIMIT/full/multiple scans. Outer replan group cleanup needs coverage; the existing malformed fixture and three Iceberg edge cases also remain gaps.
- Expected results: Hive expectations match the added scheduling configuration. Iceberg IDs 0..191 imply 192 rows/distinct IDs and sum 18336; UNION gives 384 and 36672, and LIMIT counts are 10. Actual execution and output-generation provenance were not independently established.
- Observability: pool size, active/queued tasks, group count and completions have metrics; existing scan profiles, atomic manifest counters and contextual errors remain. No additional necessary observability omission was verified.
- Transactions/persistence: no EditLog, committed-data visibility or persisted-version protocol changes. Read-transaction cleanup remains classloader-pinned; the lifetime issues are identified separately.
- Writes/atomicity: no new data mutation or commit protocol; write-format inference preserves its previous resolution/failure behavior. No additional crash/atomicity regression was verified.
- FE/BE propagation: no new wire variable. Compaction preserves all inner descriptors, parameter/volume/location boundaries and separate split sources; both BE readers drain every inner range and remote EOF requires an empty response.
- Performance/resources: bounded task queues, one pending producer batch, manifest windows/read budgets, capped hash candidates and per-producer path reuse address the intended costs. Scanner parallelism is retained. The new permanent group retention is the resource exception; reported benchmarks were not rerun.
- Further review: independently challenged continuation overlap, callback reentry, option defaults, historical cache load orders, delete matching, ORC initialization and both retry loops. Only the outer-replan scheduler leak adds a distinct finding beyond the existing threads.
Review converged after 2 rounds: all three latest reviewers returned NO_NEW_VALUABLE_FINDINGS on the unchanged one-comment set. All changed paths, candidates and risk focuses have explicit dispositions; no unresolved review question or uncovered changed path remains. Validation was static only: no builds, tests, runtime stress or benchmark reruns, and no repository source changes.
Review converged after 2 round(s).
FE UT Coverage ReportIncrement line coverage |
TPC-H: Total hot run time: 28445 ms |
TPC-DS: Total hot run time: 150530 ms |
ClickBench: Total hot run time: 24.26 s |
eadfc6f to
8b6086f
Compare
|
run buildall |
|
/review |
There was a problem hiding this comment.
Reviewed all 78 changed paths at 8b6086f, including production code, deleted code, tests, documentation and expected results. No additional distinct inline findings were substantiated. This is a static review, not an approval of the unresolved issues already discussed.
Five existing P2 findings still apply and are not reposted: outer replan scope/task-group leak, producer test envelopes incompatible with compaction, prefetch callable outliving its table/FileIO owner, recovery-schema partition-name collision, and retained manifests after historical-schema cleanup. No existing P0/P1 finding was present or independently confirmed, so existing_blocking_comment_ids is empty.
Critical checkpoint conclusions:
- Goal and evidence: separate SPLIT/METADATA workers, cooperative producer continuations, bounded manifest prefetch and transport compaction implement the intended scheduling design. Added unit and regression tests exercise the intended behavior; the five known defects above remain.
- Scope and reuse: scheduling, per-producer path reuse, backend candidate bounds and transport helpers form a coherent scan-planning change. The removed read-format cache is replaced by existing per-file format metadata; write-side format resolution remains intact.
- Concurrency and locking: traced request, producer, metadata, RPC and cancellation threads through queue admission, listener registration, readiness and terminal notifications. Remote loads and future waits occur outside the manifest pending lock; callback and queue lock ordering did not establish another defect. Scheduling is cooperative and cannot preempt an SDK call that does not return.
- Lifecycle: checked queued/running futures, backpressure suspension, early LIMIT, planning failure, EXPLAIN, retry and deferred Arrow Flight cleanup. Existing owner-lifetime and outer-retry findings cover the verified remaining failures; no additional lifetime issue was substantiated.
- Configuration: worker counts are startup settings; the bounded manifest concurrency and initial batch size are validated session settings and forwarded where required. Initial delivery limits real splits before compaction and does not suspend producer lookahead.
- Compatibility: connector API major version advances to 19.0 with the changed public surface baseline; ConnectorMetadata's baseline surface is unchanged. Both FE plan builders preserve existing FE/BE descriptor fields, and BE already flattens multiple ranges per envelope. No Thrift/protobuf definitions or persisted format changes occur.
- Parallel paths: reviewed synchronous/streaming/COUNT planning, Hive partition lanes, native/JNI metadata reads, V1/V2 scanners, mixed ORC/Parquet files, row-id fetches and write-format callers. Per-range physical formats and Iceberg field-id mappings remain authoritative.
- Conditions and errors: examined admission/full/closed states, cancellation during permit waits, empty or filtered scans, unsupported formats and lossy historical conversions. Failures propagate through their established error channels; known schema-recovery defects are covered by the existing threads.
- Test coverage and expected results: inspected admission, fairness, cancellation, wakeup, bounded-window, promotion/delete, transport serialization and path-identity tests, plus Hive and Iceberg regression cases. Expected aggregate results are internally consistent. The remaining malformed fixture is the existing test finding linked above.
- Observability: new pool/active/queued/completed/group metrics complement existing scan profiles and cache counters; no additional observability defect was established.
- Persistence and failover: no EditLog, internal partition-version, rowset or delete-bitmap changes. Snapshot pinning and delete-file metadata were traced through cache adaptation before residual/delete matching; the two known historical-spec recovery issues remain.
- Writes, transactionality and crashes: write-format property/inference behavior remains, and no write commit or atomicity protocol changes were introduced. Cancellation/resource failure analysis is covered by the lifecycle conclusions above.
- FE/BE variable passing: transport compaction retains per-file descriptors and existing fields in both plan builders; new session controls are consumed in FE rather than requiring new BE wire fields.
- Performance and memory: inspected bounded task queues/read windows, short-held locks, per-producer path reuse and physical-backend candidate clamping. Synchronous compaction retains the scanner concurrency upper bound. No additional static performance regression was substantiated; performance claims were not independently benchmarked.
- Further issues and user focus: no additional focus was supplied. The final missed-path sweep found no unresolved static candidate or uncovered changed path.
Converged in 1 review round: all three assigned reviewers, including the separate risk reviewer, returned NO_NEW_VALUABLE_FINDINGS on the unchanged empty new-comment set. Builds, tests and runtime/remote-I/O timing validation were not performed under this review-only contract. Delivery verification is separate from convergence.
Review converged after 1 round(s).
FE UT Coverage ReportIncrement line coverage |
TPC-H: Total hot run time: 28212 ms |
TPC-DS: Total hot run time: 150104 ms |
ClickBench: Total hot run time: 24.42 s |
FE Regression Coverage ReportIncrement line coverage |
8b6086f to
ff44a87
Compare
|
run buildall |
|
PR approved by at least one committer and no changes requested. |
FE Regression Coverage ReportIncrement line coverage |
…69718) ### What problem does this PR solve? Issue Number: None Related PR: #69298, #69566, #66530 Problem Summary: #### In short A statement's connector state could outlive the statement in two ways on master. A split producer still inside a read when the statement ends went on to load values into the already-closed connector statement scope, such as an Iceberg table borrowed from the table cache, and nothing ever closed them. `EXPLAIN REFRESH MATERIALIZED VIEW` plans on a context of its own that has no query id, so its scans registered their query-finish cleanup under `""` and its connector scope was never closed: each such EXPLAIN over an external base table left one registry entry and one open scope behind, without bound. This PR makes a closed scope refuse to load, and finishes the explain context's own statement when the EXPLAIN ends. This was found while checking master against branch-4.x's #66473 (scan reuse, ported as #66530). branch-4.x has neither the connector statement scope nor the query-finish registry, and its statement resources already refuse registration once closed. #### Background - Every statement has a connector statement scope (`ConnectorStatementScopeImpl`, hung on the `StatementContext`). Connectors keep what the statement shares in it: the `ConnectorMetadata` of each catalog, Iceberg table leases borrowed from the table cache (returning one is what lets the cache close the table's FileIO when it evicts it), Iceberg tracked tables, write transactions, and the query's task groups since #69298. `closeAll()` releases them when the statement ends. - A scan registers two query-finish callbacks under the query id when it starts planning splits: one stops its split producers, the other (`QueryFinishCleanup`) releases a read transaction and closes the scope. #69298 runs them at the end of every attempt, also for EXPLAIN and for plans that fail; #69566 does the same for prepared EXECUTE. - Batch and streaming scans plan on split producers. A producer calls into the connector off the statement thread, for example Iceberg's `streamSplits`, which resolves its table through the scope. #### The problem, and what it cost 1. **Late producers load into the closed scope.** Stopping a scan only flags its producers (`SplitProducer.close()`), and closing the query's task groups cancels queued work but deliberately does not interrupt metadata IO. A producer already inside `getNextBatch` keeps going after `closeAll()`. `closeAll()` had also cleared the scope, so the producer found nothing and loaded again: Iceberg borrowed a new table lease, Hive partition batches built a new `ConnectorMetadata`. Both stayed in the closed scope forever, so the lease was never returned. A value whose load was in flight while `closeAll()` ran was missed the same way. This happens whenever a statement ends while a producer is still reading: an EXPLAIN of a large table (EXPLAIN also starts batch scans), a LIMIT that finishes early, a KILL, a timeout. Nothing fails and no result is wrong; the FE just keeps the leases, metadata and FileIO alive. 2. **`EXPLAIN REFRESH MATERIALIZED VIEW` leaks its own statement.** It plans on an MTMV context of its own (`RefreshMTMVCommand.getExplainConnectContext`), and that context had no query id, so the connector sessions of its scans had `""` as their query id and registered their cleanup under `""`. The end of the outer statement finishes only the outer query id; nothing finishes `""`, and it could not be finished at the end of one statement anyway, since every context without a query id shares it. The explain context's `StatementContext` was never closed either. | Before this PR | Effect | |---|---| | A producer reading when the statement ends | Borrows a new table lease (or builds a new metadata) into the closed scope; never released | | `EXPLAIN REFRESH MATERIALIZED VIEW` over an external base table | One permanent registry entry under `""` and one open connector scope per execution | #### How this PR fixes it - `ConnectorStatementScopeImpl`: after `closeAll()` the statement is over, so `computeIfAbsent` (and with it `computeIfAbsentForPlanning` and `getOrCreateMetadata`) does not run the loader and throws `CancellationException`, as the query's task groups already do once closed; Iceberg's planning paths treat it as "the query stopped" and do not fall back to the SDK. A load that raced the close is released by its caller, because `closeAll()` could not see it. `closeAll()` now removes each entry before releasing its value, still finalizing transactions first, so every value is released exactly once. `getIfPresent` returns nothing once closed. - `StatementContext.getConnectorStatementScopeForNewSession()`: a session built on a statement whose scope is already closed (the lineage of an INSERT, after the insert finished its query, say) captures `ConnectorStatementScope.NONE` and the direct task scheduler, as offline work does, instead of failing in or loading into the closed scope. Sessions captured before the close keep the closed scope, so a producer holding one stops at its next load. - `ExplainCommand`: an explain context of its own gets a query id, and when the EXPLAIN ends, whether planning succeeded or not, it runs that query id's query-finish callbacks and closes the context's `StatementContext`, as `MTMVTask` does for a refresh. The thread-local context is still restored if that cleanup throws. - Comments that said EXPLAIN never registers a query-finish cleanup and that `closeAll()` runs after the producers have quiesced are corrected; the SPI javadoc of `computeIfAbsent` states the closed-scope behavior. #### Results | | Before | After | |---|---|---| | Producer resolving its table after the statement closed the scope | New lease borrowed, never returned | `CancellationException`, nothing borrowed | | Value whose load raced `closeAll()` | Kept in the closed scope, never closed | Closed once by its caller | | Session built after the scope closed | Loads task groups into the closed scope | Offline session (no scope, direct scheduler) | | `EXPLAIN REFRESH MATERIALIZED VIEW` | Entry under `""` and open scope left behind | Its callbacks run once, its scope closed | New tests: - `ConnectorStatementScopeTest`: `closedScopeRefusesToLoadAndHandsOutNothing`, `valueLoadedWhileTheScopeClosesIsReleasedOnceByItsCaller` (the loader blocks on another thread while `closeAll()` runs), `sessionBuiltAfterTheScopeClosesRunsAsOfflineWork`. - `SplitProducerTest.readThatOutlivesItsStatementLoadsNothingIntoTheClosedScope`: a producer is stopped inside `getNextBatch`, the scope closes, then the read resolves its table. - `ExplainRefreshMTMVCommandTest.testExplainCommandFinishesTheStatementOfItsOwnContext`: planning registers a callback under the explain context's query id and keeps a closeable in its scope; after the EXPLAIN, both succeeding and failing, the callback ran once and the value was closed. Each new test was checked against a mutation of the code it guards; every mutation turns its test red: | Mutation | Red test | |---|---| | Drop the closed check before loading | `closedScopeRefusesToLoadAndHandsOutNothing` (the loader ran twice), `readThatOutlivesItsStatementLoadsNothingIntoTheClosedScope` (the late read loaded once) | | Drop the closed check after loading | `valueLoadedWhileTheScopeClosesIsReleasedOnceByItsCaller` (the caller got the value) | | Let a new session capture the closed scope | `sessionBuiltAfterTheScopeClosesRunsAsOfflineWork` (building the session throws: the scope cannot load `engine:query-task-groups`) | | Do not finish the explain context | `testExplainCommandFinishesTheStatementOfItsOwnContext` (callback never ran) | | Give the explain context no query id | `testExplainCommandFinishesTheStatementOfItsOwnContext` (planned without a query id) |
…hema (#69488) ### What problem does this PR solve? Issue Number: None Related PR: #66538, #57531, #65851 Problem Summary: **In short.** On an Iceberg table, `DEFAULT(column)` anywhere except `UPDATE ... SET` and `MERGE ... VALUES` reads the default from the read schema, which carries no Iceberg write-default. This PR resolves it against the write schema that the statement pinned for its target, as branch-4.1 and branch-4.2 do. **Background.** An Iceberg `write-default` applies to rows written after it is set. So it lives in the writer schema that a write statement pins (`IcebergWriteSchemaContext`), not in the columns of the read schema. #65851 resolves `DEFAULT(column)` against that pinned schema in `UPDATE ... SET`, `MERGE ... UPDATE SET` and `MERGE ... INSERT VALUES` (`ConnectorWriteSchemaUtils`). Every other `DEFAULT(column)` either fails at binding or reaches `RewriteDefaultExpression` (#57531), which reads the default from the bound column. branch-4.1 #66538, also in branch-4.2, resolves these against the pinned write target too. It rejects `DEFAULT(column)` on an Iceberg column in a statement that does not write the table. **The problem, and what it cost.** Reproduced on a local cluster with a `file://` format-version 3 table: `CREATE TABLE t (id INT, c INT)`, `ALTER TABLE t ADD COLUMN d INT DEFAULT 42`, one row `(0, 0)`, so `d = 42`. | Statement | Master today | After this PR (same as branch-4.2) | |---|---|---| | `INSERT INTO t (id, c, d) VALUES (1, 1, DEFAULT(d))` | `Unknown column 'd' in 'table list'` | writes 42 | | `INSERT INTO t (id, c, d) SELECT 2, 2, DEFAULT(d) FROM t WHERE id = 0` | writes NULL | writes 42 | | `INSERT INTO t (id, c, d) SELECT 3, 3, DEFAULT(d)` | `Unknown column 'd'` | writes 42 | | `UPDATE t SET c = 100 WHERE d = DEFAULT(d)` | updates nothing: `d = NULL` makes the plan an empty set | updates every row, `d = 42` is pushed down to the scan | | `MERGE INTO t USING (SELECT 0 AS id) s ON t.id = s.id AND t.d = DEFAULT(t.d) WHEN MATCHED THEN UPDATE SET c = 300` | matches nothing | updates row 0 | | `SELECT DEFAULT(d) FROM t` | NULL | `DEFAULT(d) must reference a column of the table that this statement writes` | The `UPDATE` and `MERGE` cases fail silently: the statement succeeds and changes nothing. **How this PR fixes it.** - fe-core cannot tell from a column whether its connector keeps defaults in the read schema. Hive does: an HMS default shows up in both schemas. Iceberg does not. The new connector capability `SUPPORTS_WRITE_ONLY_DEFAULTS` says which kind a table is. The Iceberg connector declares it. The Hive connector passes it on to iceberg-on-HMS tables, like the other per-table capabilities, but only through the two-argument `getTableSchema`. Since #69299, a statement that pins the table at its latest snapshot reads the schema from the snapshot overload, which reflects none of them, so on an iceberg-on-HMS table such a statement keeps master's results (for example, `UPDATE t SET c = 100 WHERE d = DEFAULT(d)` still changes no row). Reflecting the sibling's capabilities there changes planning for all of them and is left to a follow-up PR. - For a target that declares it, `InsertUtils.normalizePlan` resolves `DEFAULT(column)` in `INSERT ... VALUES` and in the select lists of `INSERT ... SELECT`, including the replacements of `SELECT * REPLACE(...)`, against the pinned write schema. It does this by name, before the source relations are bound, so the name always refers to a target column, as in branch-4.2. In MySQL, `DEFAULT(column)` in a `SELECT` refers to a source column instead. A derived table in `FROM` is a query of its own and is left alone: `DEFAULT(column)` in it still names a column of its own `FROM` clause. - `RewriteDefaultExpression` takes the default for a `DEFAULT(column)` bound to such a table from the pinned write column with the same field id. That covers the `WHERE` of `UPDATE` and the `ON` of `MERGE`. A MERGE resolves it whatever its WHEN branches, also when they all delete, as branch-4.2 does. If the statement pinned no write schema for that table, it fails. A `DELETE` pins the writer schema too, for its delete sink, but it writes no row values, so `RowLevelDmlCommand` marks its target and `DEFAULT(column)` on it fails with `DEFAULT(d) cannot be used in a DELETE: the defaults of this table apply only to the rows that a statement writes`, as in branch-4.2, whose DELETE installs no write schema context. Master reads NULL there, so `DELETE FROM t WHERE d = DEFAULT(d)` deletes nothing; resolved to the write-default it would delete every row with `d = 42`. - A resolved default is cast to the type of its writer column before the expression around it is bound, as `RewriteDefaultExpression` already does for a bound `DEFAULT(column)`. The parsed literal `-1` is a TINYINT, so without the cast `bit_count(DEFAULT(c))` on an INT column would count 8 bits instead of 32. The external `UPDATE`, `MERGE` and changelog paths use the same helper. `RewriteDefaultExpression` rejects a bound `DEFAULT(column)` whose writer column has another type than the read column the statement was bound with, which happens only when the table changed outside Doris after Doris cached it, and asks to refresh the table: the expressions around it were bound against the read type. - Hive, Paimon and internal tables do not declare the capability, so they behave as before. - The new enum constant changes the connector plugin API surface, so `connector.plugin.api.version` goes from 26.0 to 27.0, as for the other recent changes to that surface (#69299, #69588, #69298, #69683 and #69835 took 22.0 to 26.0). **Where `DEFAULT(column)` resolves on an Iceberg target (direct Iceberg catalogs).** | Statement | Position of `DEFAULT(column)` | Result | branch-4.2 | |---|---|---|---| | `INSERT` / `INSERT OVERWRITE` | `VALUES` rows | the target column's write-default, typed as the column | same | | | the INSERT's own select lists (with or without `FROM`, set operation branches, `SELECT * REPLACE`) | the target column's write-default, by name, before binding | same, except `* REPLACE`, which branch-4.2 binds to the source | | | `GROUP BY` / `ROLLUP` outputs, a derived table in `FROM`, `WITH` definitions, subqueries, `WHERE` / `ON` | bound like any `DEFAULT(column)` (last row) | same, except derived tables, which branch-4.2 resolves against the target | | | a `VALUES` branch of a set operation | fails with an unknown column, as on master | same | | | a qualifier other than the target (`DEFAULT(src.c)`, `DEFAULT(ctl.db.t.c)`) in the positions above | fails: `column in assignment list is invalid` | same | | `UPDATE` | `SET` / `WHERE` | write-default | same | | `MERGE` | `ON` and `WHEN ... AND` conditions, whatever the WHEN branches, also when they all delete | write-default | same | | | `UPDATE SET` / `INSERT VALUES` | write-default | same | | `DELETE` (also `DELETE ... USING`) | anywhere | fails: `cannot be used in a DELETE` | fails: its DELETE has no write schema context | | any | a bound `DEFAULT(x.c)` | `x` is the written table: its write-default by field id; an OLAP or Hive table: that column's default; another Iceberg table: fails with `must reference a column of the table that this statement writes` | same | A bound `DEFAULT(column)` whose writer column has another type than the read column fails and asks to refresh the table. Where `DEFAULT(column)` is bound rather than resolved by name (the third row), it keeps its meaning from before this PR and names a column of its own `FROM` clause, as on master and branch-4.2: for example in `INSERT INTO t SELECT id, DEFAULT(d) FROM src GROUP BY id, d` with an OLAP or Hive `src`, it takes the default of `src.d`. **Results.** - Local cluster, with the table above: the table shows what each statement did before and after. `EXPLAIN UPDATE t SET c = 5 WHERE d = DEFAULT(d)` goes from an empty set to a scan with `d = 42` pushed down to Iceberg. - Unit tests: 6 new tests in `InsertUtilsTest`, 6 in the new `RewriteDefaultExpressionTest` and 1 in `RowLevelDmlCommandTest`. They cover VALUES, and INSERT ... SELECT both from a staging relation whose own column is also `name` and without FROM: each resolves to the pinned default, and an unknown column fails. `DEFAULT(column)` inside a derived table, qualified or not and also under a UNION branch, is left to binding. A `SELECT * REPLACE(DEFAULT(name) AS name)` replacement resolves like the rest of the select list and keeps the star's qualifier and `EXCEPT` list. The pinned write column wins over the read column. A statement that writes nothing fails. A table without the capability is unchanged. With master's `InsertUtils` and `RewriteDefaultExpression`, the 5 tests of the fix fail and the 2 tests of the unchanged path pass. The `SELECT * REPLACE` test fails with this PR's previous `InsertUtils`, where the replacement stays `DEFAULT(name)`. `IcebergConnectorTest`, `HiveConnectorMetadataSiblingDelegationTest` and `ConnectorPluginSurfaceTest` pass. So do the fe-core tests around the write schema: `ConnectorWriteSchemaUtilsTest`, `ExternalRowLevelUpdatePlanBuilderTest`, `ExternalRowLevelMergePlanBuilderTest`, `ChangelogRowLevelDmlTransformTest` and `PluginDrivenExternalTableTest`. Two of the tests pin the typing: `bit_count(DEFAULT(bits))` gets the default as an INT in VALUES and in SELECT, and fails without the cast; a BIGINT writer column over an INT read column is rejected, directly and under an IF bound to INT. `DEFAULT(column)` on the target of a DELETE fails even with a pinned write-default, and only a DELETE marks its target (an UPDATE does not); skipping the check, or marking UPDATE instead of DELETE, makes exactly these two tests fail. `PositionDeleteRowLevelDmlTransformTest`, `PhysicalPlanTranslatorIcebergRowLevelDmlTest`, `DeleteFromCommandTest` and `UpdateCommandTest` pass. - Regression: `test_iceberg_initial_defaults` now writes rows 20 to 22 with `DEFAULT(default_int)` in VALUES and in INSERT ... SELECT with and without FROM. It then updates them with `WHERE default_int = DEFAULT(default_int)` and `MERGE ... ON ... = DEFAULT(...)`, for Parquet and ORC, and checks the rows in the `.out`. A plain `SELECT DEFAULT(default_int)` must fail, and so must a `DELETE ... WHERE default_int = DEFAULT(default_int)` on rows 20 to 22, which hold the write-default. The suite passed locally before the DELETE block was added, and the whole suite, DELETE block included, has since passed in two External regression runs on CI. With master's FE it fails at the first of these statements: `Unknown column 'default_int' in 'table list'`.
### What problem does this PR solve? Issue Number: None Related PR: apache#69298 Problem Summary: Cold Hive scans now plan file splits through the bounded streaming path. With hive.ignore_absent_partitions=false, a missing partition raises a UserException wrapped by the file cache. The streaming completion callback exposed only the outer cache message, losing the missing-partition reason previously shown by synchronous planning. Reuse one Hive scan error conversion in both paths to retain the table name, underlying reason and original cause. The missing-partition policy and error status are unchanged. ### Release note Preserve actionable Hive file-listing errors during streaming scan planning. ### Check List (For Author) - Test: Unit Test: the new cold-streaming initialization test reproduced the reported message loss before the fix; all 94 selected FE tests passed afterward, including synchronous parity, streaming, cancellation and delivery cases. Full build.sh --fe package build and Checkstyle passed. The external-service regression was not run locally and will be re-triggered on the updated PR. - Behavior changed: Yes, asynchronous Hive split-planning errors retain the underlying cause text and table context, matching the existing synchronous path. - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#69298 Problem Summary: The active-read cancellation test for external-file split planning waited for the source-close latch and then immediately required the producer completion counter to be one. SplitProducer closes the source before invoking its completion callback, so the test can legitimately observe zero completions and fail FE CI. Hold the test read active across cancellation, force a controlled close/completion gap, and wait for a callback-completed latch before checking terminal counts. This deterministically verifies exactly one close and one completion without depending on thread scheduling. ### Release note None ### Check List (For Author) - Test: Unit Test - Reproduced the exact CI assertion failure with a deterministic close barrier before fixing the completion wait. - All 62 related FE tests pass: SplitProducerTest, SplitAssignmentTest, SplitSourceTest, HiveStreamingSplitTest and FairTaskExecutorTest. - Full FE packaging and Checkstyle pass through build.sh --fe -j4 (UI build disabled). - Behavior changed: No (test synchronization only) - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298 Problem Summary: Statement cleanup can stop a Hudi split assignment before its filesystem-view owner publishes the sync thread. Reject an already-stopped owner before remote sync and preserve terminal lease cleanup. Iceberg streaming scans adapt historical identity partitions to the selected schema, but DATE-to-nanosecond timestamp conversion was missing and dropped promoted fields could lose equality deletes. Preserve physical partition types in delete manifests, convert temporal values exactly, and normalize dropped partition keys in query-local views. ### Release note Fix cancelled Hudi planning and Iceberg scans after DATE partition columns are promoted to nanosecond timestamps or removed. ### Check List (For Author) - Test: Unit Test: 339 related FE tests; full FE packaging and Checkstyle via build.sh --fe - Behavior changed: Yes; reject cancelled Hudi sync and preserve historical temporal partitions and equality deletes - Does this need documentation: No
…views ### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298 Problem Summary: Historical DATE identity partitions rebound to ordinary microsecond timestamps fail during lazy manifest planning, and dropped DATE/timestamp partition keys use incompatible units and Java representations that can omit equality deletes. Add exact DATE/microsecond conversions and a common lossless dropped temporal-key representation while rejecting overflow, lossy narrowing and unsupported zoned promotions. Adapted content-file views also lack Serializable, causing non-batch statement task reuse to fail and fall back to SDK planning. Make the static data/delete wrappers serializable without capturing query resources, and cover actual serialized reuse across equivalent relations. ### Release note Fix Iceberg scans and equality-delete matching after DATE partition evolution to microsecond timestamps, and preserve statement task reuse for adapted manifest files. ### Check List (For Author) - Test: Unit Test: 253 related FE tests; full FE packaging and Checkstyle via build.sh --fe - Behavior changed: Yes; read historical temporal partitions correctly and reuse serialized adapted scan tasks - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298, apache#69986 Problem Summary: Hive streaming batches may span several partitions and a single partition may span many batches. Using the outgoing batch size as a per-partition split estimate therefore undercounts large partitions and overcounts several small ones. Accumulate generated splits locally for the current selected partition, publish the existing global maximum at yields and partition boundaries, and reset only when advancing partitions. Keep lazy enumeration, the ten-split floor and int saturation. The three new external scan scheduler sizes also accepted nonpositive startup values, failing later in scheduler construction or the first query group. Add a pure positive-integer validator to ordinary and custom startup configuration loading while preserving empty defaults, immutability and the exclusion of runtime callbacks with side effects. ### Release note Correct Hive split estimates during streaming planning and reject nonpositive external scan scheduler sizes at startup. ### Check List (For Author) - Test: Unit tests and packaging - Reproduced both configuration load failures and both split-estimate failures before repair. - All 147 related FE tests passed: 13 fe-common and 134 fe-core tests. - Full FE packaging and Checkstyle passed; git diff --check passed. - No local complete P0 or external-service regression in this cycle. - Behavior changed: Yes. Partition estimates accumulate independently of outgoing batch sizes, and nonpositive scheduler sizes are rejected during startup before the invalid field is updated. Lazy enumeration and existing scheduling/cancellation behavior are preserved. - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298, apache#69986 Problem Summary: Cancelled Iceberg planning can retain an independent table-generation lease when task-group lookup throws before producer cleanup is registered. Resolve that executor before acquiring the lease. Hive and Iceberg also eagerly allocate a configured maximum batch capacity, so a large valid setting can fail with OOM even for empty work; share a bounded initial allocation while preserving the configured batch maximum. Hive streaming COUNT(*) decides whether to split one partition at a time, producing excessive ranges and footer reads when the selected scan already has enough files. Use the existing concurrent cancellable producers for bounded metadata lookahead, make the scan-wide decision after all lookahead workers finish, and reuse captured metadata before lazily listing further partitions. Preserve singleton listing, fair task admission, interruption, range backpressure and the eager COUNT policy. ### Release note Avoid excessive Hive COUNT(*) scan ranges, premature memory allocation for large split batch settings, and leaked Iceberg planning references after cancellation. ### Check List (For Author) - Test: Unit tests and packaging - Reproduced the Iceberg lease leak and Hive COUNT range regression before repair; isolated the allocation failure in a separate 64MB JDK process. - All 218 selected FE tests passed with no failures, errors or skips, using run-fe-ut.sh. - Covered both scanner versions, Parquet/ORC, metadata reuse without file caching, few-file splitting, maximum batch settings, metadata failure and lookahead cancellation. - Full FE packaging and Checkstyle passed using build.sh --fe -j4; git diff --check passed. - Complete P0 and external-service regression were not run locally in this cycle. - Behavior changed: Yes. Streaming COUNT(*) uses the selected scan's split decision, large batch limits allocate only for actual work, and failed admission acquires no independent generation reference. Scheduling and cancellation continue through existing producers. - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298, apache#69986 Problem Summary: Hudi batch planning registers its filesystem-view owner directly as a statement resource. Closing the statement after GetFlightInfo therefore stops SplitAssignment and cancels its producer before a deferred Arrow Flight query can fetch splits during DoGet. The existing scan-level deferred cleanup guard cannot protect this independently registered owner. Register a statement-only cleanup wrapper that captures the query executor and checks its deferred flag at close time, while keeping the actual owner as an unconditional SplitAssignment resource for coordinator finalization and query cancellation. Preserve duplicate registration failure cleanup, task accounting, interruption and exact terminal lease release. The captured executor remains valid even if connection cleanup clears its current executor reference. ### Release note Keep deferred Hudi Arrow Flight batch scans alive until query finalization or cancellation. ### Check List (For Author) - Test: Unit tests and packaging - Reproduced the premature assignment stop through real HudiScanNode.startSplit and StatementContext.close before repair. - Added deferred, ordinary and absent-executor controls; verified registered source lifetime, forced scan stop, queued producer cancellation and one lease release. - All 90 selected FE tests passed with no failures, errors or skips using run-fe-ut.sh. - Full FE packaging and Checkstyle passed using build.sh --fe -j4; git diff --check and final source hashes passed. - No live Arrow Flight/Hudi external-service regression was run locally in this cycle. - Behavior changed: Yes. Deferred Hudi scans survive statement cleanup, while non-deferred cleanup and explicit query cancellation still stop the batch owner. - Does this need documentation: No
…nning ### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298, apache#69986 Problem Summary: External metadata planning can monopolize shared workers and retain eagerly planned split graphs. Backport bounded lazy production and fair scheduling to branch-4.2, retaining generation ownership and previous review fixes. Integrate with the release branch's individual scan-range queues, per-backend cancellation, live empty-batch protocol, and Iceberg partition/cache versioning. Transfer Hudi partition admission and filesystem-view ownership through asynchronous delivery so queue backpressure neither blocks workers nor permits unbounded pending partitions. ### Release note Bound and fairly schedule external scan planning. ### Check List (For Author) - Test: 386 focused FE unit tests passed through run-fe-ut.sh; full FE packaging and integrated Checkstyle passed through build.sh --fe -j4. External-service regression was not rerun locally. - Behavior changed: Yes. External scan planning uses bounded lazy production and fair scheduling while preserving branch-4.2 delivery semantics. - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298, apache#69986 Problem Summary: HiveSplitEstimateTest still mocks the former addToQueue delivery path. The fair producer reads a zero batch size from that mock and emits no files, so four current-head FE tests fail before verifying their estimates. Use a real SplitAssignment and SplitSource with deterministic backend assignment, retain the existing file-count and estimate assertions, and stop the assignment during cleanup. ### Release note None ### Check List (For Author) - Test: Reproduced four failures locally; all 35 focused FE tests passed through run-fe-ut.sh. Full FE packaging and integrated Checkstyle passed through build.sh --fe -j4. - Behavior changed: No - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#69298, apache#69986 Problem Summary: The cold file-count regression requests EXPLAIN VERBOSE while asserting approximate streaming split counts. Branch-4.2 deliberately disables batch mode for VERBOSE so it can enumerate exact file details. Use normal EXPLAIN for this streaming-selection assertion, matching the suite's other streaming checks, and retain all expected data results. ### Release note None ### Check List (For Author) - Test: 21 focused Hive FE tests passed through run-fe-ut.sh, covering exact VERBOSE mode and cold streaming selection; git diff --check. External Hive regression is not available locally and must be verified by remote CI. - Behavior changed: No - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#69298 Problem Summary: External split production and Iceberg manifest reads moved from the registered scheduleExecutor to FairTaskExecutor instances constructed directly by ExternalMetaCacheMgr. The existing thread_pool metrics therefore no longer showed the migrated workers, queued work or progress. A production-entry unit test reproduced zero registered NotCheckpointExternalSplit pool-size metrics before the fix. Create these schedulers through ThreadPoolManager and include their existing counters in its startup metric registration, using distinct normal and checkpoint names. Latch-controlled real scheduler tests verify active, queued, completed and group values through MetricRepo. Scheduling and delivery behavior are unchanged. ### Release note External split and metadata schedulers now expose thread_pool metrics for worker count, active tasks, queued tasks, completed tasks and task groups. ### Check List (For Author) - Test: Unit Test and build - 45 focused FE tests passed with zero failures, errors or skips via run-fe-ut.sh; the new production-entry metric test failed before the fix and passes after it. - build.sh --fe -j4 including Checkstyle passed. - Behavior changed: Yes, export metrics for the replacement schedulers; query scheduling and scan delivery are unchanged. - Does this need documentation: No, existing thread_pool metric labels are reused.
What problem does this PR solve?
Issue Number: None
Problem Summary: External scans can monopolize shared FIFO workers, block on metadata submitted to the same pool, repeat path/backend-assignment work, and load cold Iceberg manifests serially. This change uses query-owned bounded task groups, separate split and metadata workers, cooperative continuations and bounded completion-order manifest prefetch. Early stop and exceptions close query-owned tasks and resources, including planning failures before a scan can register its finish callback. Path reuse, bounded consistent-hash candidate lookup and compatible range compaction stay with scheduling to avoid moving the bottleneck into scan bookkeeping. File-format selection and snapshot-specific partition type adaptation preserve per-file semantics without duplicate SDK planning or changes to shared cache values.
Each backend's bounded Split channel wakes on data, completion, cancellation and failure, eliminating the empty terminal request's previous 100 ms polling delay. An earlier four-query JFR reproduction found 78 delayed EOF requests in 400 SQL executions; the fixed path has none in the subsequent replay.
Initial RPC delivery is separate from background lookahead:
split_initial_batch_size(default 64) limits the first response per backend, additionally capped by its request size. Further responses use the BE request size. Producers continue before BE consumption, limited by the configured queues and cooperative fairness. The response bound counts real splits before transport-envelope compaction. There is no producer-side wait for a first BE request.The query-level
enable_external_table_batch_modeswitch is checked at the engine entry before either partition-batch capability checks or file-streaming estimates. Setting it to false consistently selects synchronous planning; previously the partition-batch path could bypass the switch. Provider implementations still determine whether asynchronous planning is supported. Partition/file thresholds and producer/queue defaults are unchanged.Scan-local partition adaptation preserves delete matching after numeric promotion and source-schema cleanup, even when data and delete Manifest entries have different cache warmth. UNKNOWN partition targets use consistent numeric Java representations before delete-key comparison, without rewriting shared cache entries. Regression tests exercise both cache orders, identity/truncate partitions, concurrent historical/current projections and different-partition exclusion.
Split channels and their capacity waiters are indexed by backend ID. This avoids repeatedly hashing and boxing a Backend's endpoint/health fields for each producer batch, which was visible in CPU/alloc flamegraphs at split_batch_size=1. The immutable ID also keeps one bounded channel when a backend handle's metadata changes. Batch generation size, FIFO delivery, backpressure, wakeup ordering, cancellation and fair scheduling are retained. A focused queue/lifecycle suite has 63 passing tests, including three-backend streaming and the same-ID/different-handle capacity-wakeup boundary.
Main benefits and independent SQL measurements
This PR addresses two different concerns: bounded scheduling and query lifecycle correctness, and overlapping cold metadata I/O. Query groups rotate ready work and bound queued tasks; separate SPLIT and METADATA workers avoid parent/child pool starvation; cancellation prevents queued work and cache-to-SDK fallback from restarting a finished query. These are scheduling/resource guarantees, not a claim that every SQL statement becomes faster.
The clearest standalone latency gain is Iceberg cold full scans: approximately 32–34% less complete-SQL time. Hive already has partition-parallel listing, so warm Hive single-query latency remains approximately flat. With the same Linux AArch64 Release BE, one backend, six CPUs, warm BE footer/data cache,
split_batch_size=1andsplit_queue_capacity=4096:Baseline: frozen unoptimized master
e0bf4874070. The standalone measurement package records9b6d902ffa1(built sourced61d21b943abefore its equivalent EOF-fix amendment). Each single-query cell has 40 measured samples across forward/reverse runs after prewarm. Cold means FE catalog/Manifest invalidation, not cold JVM or BE data. These historical measurements predate the subsequent initial-delivery refinements, rebase and review repairs; they establish the standalone implementation's measured benefit and are not a fresh A/B of the current head. The total PR gain includes Manifest prefetch and scan-path improvements; it does not isolate the fairness mechanism itself.The focused initial-delivery experiments below compare two already-optimized FE versions. For example, Hive four-query LIMIT group median improves 15.38 → 11.82 ms (23.1%), while the controlled slow-listing/delayed-BE full scan stays 388.52 → 386.96 ms. These incremental results must not be added to, or substituted for, the standalone master comparison above.
Ready split batches are drained together within each BE request, capped at 64 producer batches and the number that fits the remaining request. This amortizes queue operations and per-batch channel checks without waiting to fill a response or changing producer buffering. Capacity callbacks execute after the queue lock is released; callback-published errors and cancellation are checked before returning the drained data. The scalar path still handles empty queues and partial batches.
A four-phase fixed-Release-BE comparison on a warm 10,000-file Iceberg table narrows single-query batch-one overhead against each phase's batch128 control from 5.8–6.4% to 0.7–2.8%. Four-query full scans show no stable change. The initial four-query LIMIT difference of approximately 1 ms does not reproduce in a separate 3,200-SQL reverse-order repetition; sub-millisecond differences remain noisy. CPU samples inside getNextBatch are essentially unchanged (392 versus 389), while allocation samples fall from 444 to 174, mostly boxing and iterators. These are statistical samples, not exact invocation/byte counts. Across both formal comparisons, 4,800 SQL statements pass result checks; separate cache-hit profiles, Hive correctness and Iceberg cold LIMIT startup checks also pass. Both isolated PR and integration worktrees pass 66 focused unit tests and full build.sh --fe including Checkstyle. This comparison changes only SplitSource.class; plugins and the Release BE are identical.
Release note
External Hive and Iceberg scans support bounded fair task scheduling, query cancellation and concurrent cold Manifest loading. The new
split_initial_batch_sizesession setting controls first-response size independently ofsplit_batch_sizeandsplit_queue_capacity. File formats, pinned snapshots and full-scan results are preserved.Check List (For Author)
Current-head repair validation at
28fb25d01bd:StmtExecutorTestandIcebergManifestReadContextTestpassed (69 tests);build.sh --fe -j4and Checkstyle passed. Earlier review-repair validation at17859e3ff14: 1,521 Iceberg package tests (zero failures/errors, one environment-dependent skip), two query-lifecycle tests, andbuild.sh --fe -j4including Checkstyle passed. Coverage includes cancellation before first scheduler acquisition, all four cache-to-SDK fallback paths, shared query read admission and identity/truncate data/delete manifests after source-schema cleanup. Previously reported live SQL/regression and performance experiments are historical and were not rerun after every review repair. CI status remains tracked by the checks on this PR.build.sh --fe, including Checkstyle; 750 scoped FE/SPI/Hive/Iceberg tests in 53 classes, zero failures/errors/skips. Updated Hive streaming regression and Iceberg scheduled-split regression both passed without skips. Matched Release-BE experiment: 1,200 four-query LIMIT SQL and 150 full scans, all result checked. Separate 400-query RPC/EOF diagnostic; footer/page-cache profile verification.Supporting experiments: initial delivery, full-scan overlap and RPC diagnostics
Focused initial-delivery experiment
Same Linux AArch64 Release BE, one backend, six engine CPUs, warm BE data/footer cache;
num_partitions_in_batch_mode=1,split_batch_size=1,split_queue_capacity=4096. Hive fixture: 1,000 partitions, one file per partition. The reference FE already includes the other scheduling/scan changes. It is the exact FE before the initial-delivery change, not unoptimized master or SR.Each fresh FE follows identical 45-second full-scan/LIMIT prewarm. Four-query LIMIT results have 100 groups / 400 SQL per version, without agents, profilers, builds or unit tests during timing. Every LIMIT length and full COUNT/SUM result is checked. Full scans use 10 measured SQL per cell; cold cells run forward/reverse order.
The cold-listing catalog disables only the file-list cache over the same table, with warmed partition metadata and BE data/footer caches. First-fetch delay is injected at
SplitSource.getNextBatchentry before demand callbacks; the slower-listing case injects 20 ms before each real directory request. These controlled delay-injection measurements include the sleeps and do not represent ordinary production latencies or all resource effects of a slow BE startup.A rejected producer startup barrier had the same LIMIT group median (11.85 ms), but the slower-listing/delayed-fetch full scan took 651.26 ms. Independent initial delivery restores this to 386.96 ms. During the same 300 ms delay, both the pre-change reference and final implementation complete another 896 directory listings (median); the barrier completed zero. The final source contains no startup barrier. The barrier's per-SQL LIMIT median was 10.77 ms versus 11.15 ms now; group mean, p95 and maximum all improve. Both distributions are retained in validation rather than claiming uniform per-SQL improvement.
Separate diagnostic replay of 400 LIMIT SQL has 400 Split RPCs: 399 first responses contain 64 real file ranges and one contains 25. Every first response satisfies the bound, with no extra RPC needed in these LIMIT cases and no delayed EOF request. Diagnostic query timings are excluded from the table. A full-scan profile records 1,000 footer hits / zero footer reads and 1,000 page hits / zero page misses.
Backend candidate lookup was independently measured with production ConsistentHash/Backend classes, 256 virtual nodes, JDK17/macOS ARM64: 0.21–0.26 microseconds at 2–128 backends, including four threads. That is a function benchmark, not multi-backend complete SQL. No SR performance matrix was rerun. The subsequent Iceberg matrix is recorded below with its own fixed BE identity.
Iceberg first-delivery follow-up
Matched previous/current FE (
3b97eee547c/3e94facccfb), keeping the same Linux AArch64 Release BE throughout the timed runs (SHA256 prefix26a52681f1ed). Only initial-delivery behavior differs between these FE variants; the rest of the implementation is held fixed. This BE differs from the one used in the Hive table above, so absolute values should not be mixed between the tables.Doris Manifest Cache enabled; SDK manifest cache disabled. Both use producer batch1, queue4096, manifest concurrency8, pruningfalse. Cold means catalog/manifest invalidation with hot BE data/footer caches and warm JVM. Before each cold four-query group, invalidate exactly once, outside timing; all four then run concurrently. Previous groups' background tasks drain first. Every COUNT/SUM and LIMIT length is checked. Single-query40 samples/cell; four-query30 groups/cell, forward/reverse order.2560 measured SQL.
Warm LIMIT improves17–20% single-query and26–30% group completion. Cold LIMIT is near-flat. Some full-scan points increase1–3.5%, which are disclosed rather than calling all cases faster.
An additional same-JVM parameter-only ABBA comparison (cap64 versus unlimited,20 samples/groups per mode/cell;1600 SQL) shows full scans mostly within-1.2% to+2.1%. Its1000-manifest cold single full scan had one13.6% median increase; a focused random-order repetition of50 samples/mode did not reproduce it (unlimited223.60ms versus64's223.25ms). The median-difference bootstrap95% interval is-12.90 to+8.28ms. These runs do not establish zero overhead or equal tails; they did not reproduce a consistent large full-scan regression. No product code change is made from the small mixed differences.
Separate diagnostics directly count actual Manifest-cache loader executions: cold100/1000, warm0/0. Both full-scan profiles show10000 footer hits/zero footer reads and10000 page hits/zero misses. No agent, profiler, build or UT runs during the4260 measured SQL. Supplementary RPC diagnostics are excluded from the timing claims.
Focused multi-backend small-batch follow-up
Balanced assignments now consult the existing weight-count index before constructing redistribution heaps. Successful offers and consumers with no capacity waiter bypass the query-wide capacity monitor. On a failed offer, callback registration precedes the retry under that monitor, preserving wakeups when capacity is released before registration. Listener-set mutations and error fanout remain protected, and callbacks execute outside the monitor.
A four-phase previous/current FE experiment holds the remaining code fixed and alternates reference/current/current/reference packages. The fixed Linux AArch64 Release BE uses three backends, two configured cores each, a shared six-CPU budget, 4 GiB per BE and footer-cache capacity 20000. Both variants use partition/file thresholds 1, initial response 64, producer batch1 and queue4096. All 3200 measured SQL results pass; each table cell has 80 queries/groups. Separate profiles verify zero footer reads and zero page misses on every backend. No profiler, agent, build or unit test overlaps timing.
The demonstrated benefit is reduced FE resource/lock cost: equal-workload sampling of 240 full scans reduces active CPU samples from 6385 to 4373 (31.5%) and allocation samples from 39174 to 25396 (35.2%). Recorded accumulated waits at capacity notification fall from 295.112 ms to zero above the 10-us recording threshold. Samples and accumulated parallel waits are not complete-SQL time. Full scans remain approximately flat, with the four-query median increasing 1.0%; this experiment does not establish an end-to-end full-scan speedup. The current same-version batch128/queue32 control takes 152.504 ms for four-query full scans, leaving 5.5% batch1 overhead. LIMIT is preserved. Batch128 LIMIT controls also improve in this experiment, so the full observed LIMIT difference should not be attributed solely to batch1 handling.
Both isolated PR and integration worktrees pass 120 focused policy/split/concurrency tests and full FE build/Checkstyle. Added tests cover redistribution across cumulative weight thresholds, monitor-independent uncontended paths and deterministic capacity release before waiter registration. Source and modified-class identities match between the independently built worktrees. Configuration defaults remain unchanged.