Repository navigation
Conversation
penghuo
force-pushed
the
feat/async-ppl-progress
branch
2 times, most recently
from
October 2, 2026 19:20
990091f to
58fe294
Compare
penghuo
force-pushed
the
feat/async-ppl-progress
branch
3 times, most recently
from
October 6, 2026 18:57
1d485f2 to
f1f112c
Compare
Report bounded, monotonic progress for every asynchronous Calcite PPL query, independently of whether partial rows are available. Implements opensearch-project#5797 on top of the async submit/poll lifecycle from opensearch-project#5832. Every asynchronous response — submit, poll, and terminal — carries: "progress": { "fraction_done": 0.44 } Guarantees: finite and within [0.0, 1.0]; never decreasing; at most 0.8 while RUNNING; exactly 1.0 on SUCCEEDED; and the last running value retained on failure or cancellation. The 20% reserve is not an estimate of coordinator progress — it exists so that draining every source cannot report a result that is not ready, because the coordinator may still be sorting, joining, or reducing. Progress is derived from signals OpenSearch already produces, so it needs neither track_total_hits nor partial results: - per-shard query-phase completion via SearchProgressListener, weighted by live primary document counts from one bounded _stats lookup per index expression; - page counts for point-in-time paged scans; - bucket doc_count coverage for Composite aggregations; - document-count-weighted combination across sources, falling back to equal weight when any source's estimate is unavailable. Source identity is the physical plan position, claimed during code generation and baked into each generated scan call. Calcite canonicalizes equal plan nodes, so a same-index equijoin has both of its scan positions pointing at one node object; keying on the node would report the whole plan done when the first of two reads finished. Positions are enumerated from the physical plan via PLAN_BEFORE_IMPLEMENTATION — which also makes scans inside subquery expressions visible — and the source set is sealed before anything is published, so the denominator never grows mid-query. A consumer that closes a source early (head, a coordinator-side take) completes it, but only once result draining has succeeded. A bare close is ambiguous: Linq4j closes a source from its own finally block, which runs identically whether the query is finishing or failing, and before the exception reaches the engine. Confirming at end of draining means an aborted query never rounds its abandoned sources up to done. Synchronous responses and the Spark async-query responses are unchanged and carry no progress field. The search-request wrapper that attaches the progress listener preserves the parent task, so cancellation and resource accounting are unaffected. Signed-off-by: Peng Huo <penghuo@gmail.com>
penghuo
force-pushed
the
feat/async-ppl-progress
branch
from
October 6, 2026 19:31
f1f112c to
0c40cd5
Compare
Signed-off-by: Peng Huo <penghuo@gmail.com>
Signed-off-by: Peng Huo <penghuo@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds bounded, monotonic
progress.fraction_doneto RUNNING asynchronous Calcite PPL submit responses and retained-job GET responses. Partial-result publication remains a separate feature. Implements OpenSearch SQL opensearch-project#5797, using the progress tracker design in §8 of the detailed design onfeat/ppl-progress-execution-v0at3b7e369.This PR targets
refactor/async-ppl-service-retention, the branch for upstream PR opensearch-project#5832 at56e18bf67. Its current API and query-job lifecycle define the submit, poll, retention, and error behavior. A submission that completes within its wait budget returns the existing final rows or explain response without aprogressobject. Retained responses include progress alongside the existing job fields. Immediate failures retain the existing exception response. Retained failures carry the structured synchronouserrorobject introduced by the updated base, alongside their frozenprogressvalue; both survive owner-node forwarding.OpenSearchQueryJobService creates a distinct tracker for each published job. QueryJob owns the observer, creates the progress-aware execution listeners, samples status, and freezes progress at failure/cancellation. PPLQueryRunner receives a neutral ResultListeners factory, forwards its listeners unchanged to PPLService, and handles final-result conversion; it has no progress tracker, progress getter, or progress-listener implementation.
Progress contract
The following values apply to retained jobs. A submit that finishes within
wait_for_completion_timeoutreturns the normal final response without progress or a polling ID.[0.0, 0.8]1.0Sources are registered from the final physical plan and sealed before progress is published. Each scan position has its own identity, including shared plan nodes in self-joins; generated bindings remain stable across repeated enumeration and PIT index pruning.
Single-request work uses shard query-phase callbacks. Paged searches use completed pages plus bounded in-flight progress; Composite aggregations use bucket document coverage. Bounded, best-effort primary document statistics provide shard and source weights. Missing estimates use the design's fallback calculations. This adds no forced
track_total_hitsrequirement.Coordinator limits complete their underlying sources when their output quota is reached, while other query work can continue. A scoped, core-neutral
PhysicalPlanHookapplies the equivalentProgressAwareEnumerableLimitafter physical plan selection and before code generation and source registration. The node retains its inputs, offset, fetch, traits, and cost model and delegates row production to Calcite. Closing during exception or cancellation cleanup does not complete abandoned work.Compatibility
Synchronous and Spark response shapes are preserved. Unobserved scans keep their original generated
scan()call, preserving extended explain output. Observed search requests preserve their parent task. The async core DTO retains the typed explain response introduced by opensearch-project#5832; formatting remains in the transport layer, without a new async-core dependency onprotocol.Retained queries with no observed OpenSearch source report
0.0while running and1.0on success. Progress remains an estimate of source work rather than filter selectivity or coordinator CPU work.Scope
66 files: 42 production, 23 test/support, and 1 documentation. The job-boundary refactor changes four existing production files and six existing test files, with no new repository file. Relative to the previous PR revision, OpenSearchQueryJobService.java and five existing tests now appear in the diff; QueryJobProgressTest was already included. The runner contract now accepts a neutral listener factory, and the tests adapt its callers and verify per-job isolation and listener forwarding.
The source calculator, search callbacks, source registration, coordinator-limit instrumentation, job lifecycle, and response formatting each implement part of issue opensearch-project#5797 and design §8. Scope cleanup removes an unused response helper, a write-only channel field, producer-less page events, unused response flags, and a duplicate test harness. The production planner/code-generation regression and meaningful lifecycle/source/REST coverage remain.
Validation
Implementation and local validation use retention commit
56e18bf67. The target branch has since advanced to8cb623f23through four test-only commits; those commits are not incorporated into this reviewed head. The reviewed local snapshot was committed unchanged as1ec06238f; all ten modified-file hashes match the tested snapshot. Unit counts below come from fresh per-class XML across six modules. Progress and lifecycle IT XML reports were archived independently; discovered-suite registries are excluded.spotlessCheckandgit diff --check1ec06238f1ec06238fsnapshot1ec06238fsnapshotAsyncPPLProgressIT, Calcite enabled with 40-row/bucket pages1ec06238fsnapshot, including inline rows/explain omission and retained-job progressAsyncPPLQueryLifecycleIT, including sync/inline/retained error parity1ec06238fsnapshot0c40cd5cf; 203 tests. No fresh unit rerun claimed for that moduleCalciteExplainITf1f112cf6: 262 passed, 4 skipped; no expected-fixture changesAsyncPPLMultiNodeRoutingITf1f112cf6: 4 passedf1f112cf60c40cd5cf; calculator unchanged in1ec06238f1ec06238f; checks queued/runningNew boundary regressions submit two jobs through the actual service, register the same source id in both, and prove that one job's events do not advance the other. A wiring regression emits events through the observer carried by the job-produced listener and checks the same job's GET progress. PPL tests assert that both row and explain listener instances reach PPLService unchanged and cover explain success/failure. Existing lifecycle progress races, high-water-mark freeze, null/throw fallback, and timeout behavior remain covered.
ProgressEnvelopefollows the neighbouringErrorFormatter's Lombok@UtilityClassform so it introduces no uninstantiable-constructor coverage gap, andAsyncQueryResultResponseFormatterTestcovers the progress-present branch for succeeded, running, and failed responses as well as the Spark omission. New coverage includes calculator bounds and weighted combinations, terminal-transition races, parent-task and shard-listener wiring, scan identity, inline progress omission, retained response formatting, and cleanup behavior.CoordinatorLimitProgressEngineTestexercises the production planner, code generator, registrar, and progress signal and asserts0.4before the outer search begins; removing the hook causes that assertion to fail at0.00016. The production engine test also exercises the physical-plan hook through the real statement preparation path. Zero-fetch behavior is covered at the limit wrapper; the existing synchronous planner failure for PPLhead 0is outside this change.Check List
--signoff.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.