Stream Arrow batches on the data-node fragment execution path - #21418
Conversation
PR Code Analyzer ❗AI-powered 'Code-Diff-Analyzer' found issues on commit 1b7aed7.
The table above displays the top 10 most important findings. Pull Requests Author(s): Please update your Pull Request according to the report above. Repository Maintainer(s): You can Thanks. |
PR Reviewer Guide 🔍(Review updated until commit 089de3e)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 089de3e Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 2b5a112
Suggestions up to commit 53e284b
Suggestions up to commit e6ee337
Suggestions up to commit e231cb1
Suggestions up to commit 2d2d2c1
|
b3f939a to
f5bde4e
Compare
|
Persistent review updated to latest commit f5bde4e |
5347011 to
f748228
Compare
|
Persistent review updated to latest commit 5347011 |
|
Persistent review updated to latest commit f748228 |
|
❌ Gradle check result for f748228: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
f748228 to
96923e3
Compare
|
Persistent review updated to latest commit 96923e3 |
|
❌ Gradle check result for 96923e3: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
96923e3 to
0f8c8b5
Compare
|
Persistent review updated to latest commit 0f8c8b5 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #21418 +/- ##
============================================
- Coverage 73.55% 73.51% -0.05%
+ Complexity 74490 74480 -10
============================================
Files 5970 5970
Lines 338262 338261 -1
Branches 48758 48752 -6
============================================
- Hits 248824 248656 -168
- Misses 69581 69785 +204
+ Partials 19857 19820 -37 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
0f8c8b5 to
83c0660
Compare
|
Persistent review updated to latest commit 83c0660 |
|
❌ Gradle check result for 83c0660: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
b0fbd53 to
1b7aed7
Compare
1b7aed7 to
f450ad2
Compare
|
Persistent review updated to latest commit f450ad2 |
|
Persistent review updated to latest commit 6ab8fff |
|
Persistent review updated to latest commit 53e284b |
… wrapper PR 21454 added TransportResponseHandler#skipsDeserialization() and threaded it through the user-facing wrappers (ContextRestoreResponseHandler, MetricsTrackingResponseHandler, TraceableTransportResponseHandler). It missed the anonymous wrapper in TransportService.sendRequestAsync, which wraps the handler whenever a parent task is set. Without this forward, any ArrowBatchResponseHandler reached via sendRequestAsync (i.e., any stream dispatch under a parent task) receives a non-native VectorStreamInput and throws IllegalStateException at ArrowBatchResponse.<init>. Should land in PR 21454 itself; carrying on our branch so the allocator fix can be validated today. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Today's path drains DataFusion results into Object[] rows, sends one buffered response, and the coordinator converts back to Arrow. Replace with native Arrow batches over the stream transport from opensearch-project#21253. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
analytics-engine and its child plugins (analytics-backend-datafusion, test-ppl-frontend) pass VectorSchemaRoot and ArrowBatchResponse across plugin boundaries for streaming. Cross-plugin Arrow transfer only works when the types are loaded by the same classloader, and gRPC zero-copy requires identical class identity on both ends. Declare arrow-flight-rpc as analytics-engine's extendedPlugins parent so they share a single classloader. Overlapping jars (arrow-vector, arrow-memory-core, jackson, guava, slf4j, flatbuffers) move to compileOnly to avoid duplicate bundling — the parent plugin supplies them at runtime. Switch parquet-data-format from arrow-memory-unsafe to arrow-memory-netty with the buffer-patch + netty-buffer/common transitives. With a single shared classloader, NettyAllocationManager wins the ServiceLoader lookup and satisfies the zero-copy Netty buffer path. Add the corresponding --add-opens, io.netty.tryUnsafe flags, and thirdPartyAudit ignoreViolations for netty internals. Drop the now-unused Guava thirdPartyAudit ignoreViolations from analytics-engine: runtimeClasspath excludes guava, so the listed classes are absent and forbiddenApis trips "all excluded classes seem to have no issues" if they remain. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Move the Arrow allocator from per-request to service-lifetime so every fragment on a node shares one RootAllocator owned by AnalyticsSearchService. Shape of the change: - ExecutionContext exposes get/setAllocator. - AnalyticsSearchService constructs one RootAllocator at init, closes it in close(); AnalyticsPlugin.close() forwards to the service so Node shutdown releases it. - Both the row path (executeFragment) and the streaming path (executeFragmentStreaming) inject the service allocator into ExecutionContext. The engine reads it via ctx.getAllocator() and never closes it. - The streaming handler no longer pulls an allocator off the Flight channel, so the engine/service path no longer depends on transport. Transport correctness: Arrow Flight's FlightOutboundHandler#processBatchTask creates its transfer target on the producer's allocator, so every VSR stays same-allocator end-to-end regardless of which allocator the producer picked — the cross-allocator foreign-buffer leak doesn't apply here. Also fix DatafusionSearchExecEngineTests.collectRows to close each EngineResultBatch's VSR. This is a test backfill required by the fresh-VSR-per-batch design introduced in the preceding stream-Arrow-batches commit; the allocator change here just surfaces it. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Fresh-VSR-per-batch design hands buffer ownership to the caller of next(); tests that never closed the returned batch were leaking on the test allocator and failing at tearDown. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
The fragment execution request handler receives a Task from transport (built by FragmentExecutionRequest.createTask as an AnalyticsShardTask). That task was being dropped; buildContext hard-coded null with a TODO. Pass it through executeFragment / executeFragmentStreaming into the ExecutionContext. Backends that build their own context from it (e.g. DatafusionContext) now carry the real task instead of null. Widen the task field/getter type in ExecutionContext, SearchExecutionContext, and DatafusionContext from SearchShardTask to the transport-level Task — the concrete type we receive from the request handler isn't a SearchShardTask. LuceneSearchContext.task() keeps its narrower SearchShardTask return via covariant override. The row-path cancellation check in AnalyticsSearchService.collectResponse still operates on AnalyticsShardTask directly (unchanged), so this change is purely about plumbing the task through — it doesn't alter cancellation behavior on either path. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
QueryContext used its own static RootAllocator for per-query children. That root was independent from FlightTransport.rootAllocator, so a VectorSchemaRoot arriving through Flight into DatafusionReduceSink.feed failed Arrow's AllocationManager associate check (reference equality on getRoot()). With PR 21454's ArrowAllocatorProvider in place, every Arrow plugin takes children of one node-level root. Drop the static SHARED_ROOT and get per-query allocators via ArrowAllocatorProvider.newChildAllocator. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Two issues prevented :sandbox:qa:analytics-engine-rest:integTest from starting the integTest node: 1. analytics-engine's plugin descriptor declares extendedPlugins = arrow-flight-rpc, but the testClusters block didn't install it, so plugin install failed with "Missing plugin [arrow-flight-rpc], dependency of [analytics-engine]". 2. On JDK 25, AnalyticsSearchService's RootAllocator triggers Arrow's NettyAllocationManager static init, which requires Netty unsafe access. The default test-cluster JVM args disable it, causing ExceptionInInitializerError at node start. gradle/run.gradle adds the same four io.netty.* overrides for arrow-flight-rpc; mirror them here. With both fixes the task reaches and passes its integration tests. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Mirrors CoordinatorReduceIT but enables STREAM_TRANSPORT via @LockFeatureFlag, exercising the shard-fragment → Flight → DatafusionReduceSink.feed path that previously failed with Arrow's cross-root associate check on multi-shard queries. Uses source=T (baseline scan) rather than stats sum — the aggregate path hits a separate Substrait converter gap (OpenSearchStageInputScan.SINGLETON not handled) unrelated to the allocator-root fix exercised here. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Sandbox parquet-data-format missed the netty 4.2.12 → 4.2.13 bump in PR opensearch-project#21490. Regenerated via `./gradlew :sandbox:plugins:parquet-data-format:updateSHAs`. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
53e284b to
2b5a112
Compare
|
Persistent review updated to latest commit 2b5a112 |
When a stage is already in terminal state (cancelled/failed), Arrow batches arriving from in-flight transport responses were dropped without closing, leaking buffers under the Flight client allocator. Now releaseResponseResources() closes the VectorSchemaRoot on early exit. Also replace RowResponseCodec's unreachable `new RootAllocator()` fallback with a fail-fast IllegalArgumentException — a standalone root would break Arrow's associate check and leak if ever triggered. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
|
Persistent review updated to latest commit 089de3e |
|
Breaking change check failure is because of #21143 (comment) |
…arch-project#21418) * [PR 21454 follow-up] Forward skipsDeserialization in TransportService wrapper PR 21454 added TransportResponseHandler#skipsDeserialization() and threaded it through the user-facing wrappers (ContextRestoreResponseHandler, MetricsTrackingResponseHandler, TraceableTransportResponseHandler). It missed the anonymous wrapper in TransportService.sendRequestAsync, which wraps the handler whenever a parent task is set. --------- Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
…arch-project#21418) * [PR 21454 follow-up] Forward skipsDeserialization in TransportService wrapper PR 21454 added TransportResponseHandler#skipsDeserialization() and threaded it through the user-facing wrappers (ContextRestoreResponseHandler, MetricsTrackingResponseHandler, TraceableTransportResponseHandler). It missed the anonymous wrapper in TransportService.sendRequestAsync, which wraps the handler whenever a parent task is set. --------- Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Description
Today's path drains DataFusion results into
Object[]rows, sends one buffered response, and the coordinator converts back to Arrow. This PR replaces that with native Arrow batches over the stream transport from #21253 #21437 #21454.When Arrow Flight stream transport is enabled, the data-node handler iterates
EngineResultStreambatches and sends each as anArrowBatchResponseover the streaming channel. The coordinator receives batches directly asVectorSchemaRootand feeds them into the stage'sExchangeSink. The row path is preserved as fallback when stream transport is not available.Data flow
flowchart TD subgraph Coordinator HANDLER2[StreamingResponseListener] RESP[FragmentExecutionArrowResponse<br>extends ArrowBatchResponse] SFSE[ShardFragmentStageExecution] SINK[ExchangeSink] end subgraph Data Node HANDLER[StreamingFragmentHandler] SVC[AnalyticsSearchService] FR[FragmentResources<br>reader + engine + stream] DRS[DatafusionResultStream<br>fresh VSR per batch] end SFSE -- "dispatchFragmentStreaming<br>(STREAM type)" --> HANDLER HANDLER -- "executeFragmentStreaming" --> SVC SVC -- "startFragment" --> FR FR -. "iterate batches" .-> DRS DRS -- "FragmentExecutionArrowResponse<br>per batch" --> HANDLER HANDLER -- "sendResponseBatch" --> HANDLER2 HANDLER2 -- "response.getRoot()" --> RESP RESP -- "VectorSchemaRoot" --> SINKKey types
flowchart TD subgraph coordinator [Coordinator Node] direction TB DPE[DefaultPlanExecutor] QC[QueryContext<br>bufferAllocator per query] PW[PlanWalker] SFSE2[ShardFragmentStageExecution<br>extends AbstractStageExecution<br>implements DataProducer] LSE[LocalStageExecution<br>implements SinkProvidingStageExecution] DRS2[DatafusionReduceSink<br>implements ExchangeSink] ATXS[AnalyticsSearchTransportService<br>dispatchFragmentStreaming] end subgraph datanode [Data Node] direction TB ATXS2[AnalyticsSearchTransportService<br>handler] ASS[AnalyticsSearchService<br>service-level allocator] FR2[FragmentResources<br>reader + engine + stream] DFSE[DatafusionSearchExecEngine<br>implements SearchExecEngine] DFRS[DatafusionResultStream<br>fresh VSR per batch] end subgraph shared [Shared Root] AAP[ArrowAllocatorProvider<br>node-level RootAllocator] end DPE --> QC DPE --> PW PW --> SFSE2 PW --> LSE SFSE2 --> ATXS LSE --> DRS2 DRS2 -.-> QC ATXS --> ATXS2 ATXS2 --> ASS ASS --> FR2 FR2 --> DFSE DFSE --> DFRS AAP -.-> QC AAP -.-> ASSSend side (data node)
sendResponseBatchis async — Flight's executor transfers buffers on a separate thread while the producer advances to the next batch. Reusing one VSR would race.ArrowBufreferences.AnalyticsSearchServiceowns one allocator for its lifetime, injected into engines viaExecutionContext.Receive side (coordinator)
QueryContext.bufferAllocator()lazily creates a child allocator (256 MB limit) used byExchangeSinkto hold received batches during the reduce. Closed byDefaultPlanExecutor's terminal listener when the query completes or fails.ArrowAllocatorProvider). Arrow'sassociatecheck compares root identity (reference equality). Multi-shard streaming failed atDatafusionReduceSink.feedwith "A buffer can only be associated between two allocators that share the same root" becauseFlightTransportandQueryContexthad separateRootAllocatorinstances. Both now take children of a single node-level root exposed fromarrow-flight-rpc.Cross-plugin wiring
extendedPlugins = ['arrow-flight-rpc']on analytics-engine so cross-plugin Arrow types share a classloader. Same FQN loaded by different classloaders = different Java classes;instanceofand casts fail silently. Overlapping jars (arrow-*, jackson, guava, slf4j, flatbuffers) moved tocompileOnly.TransportService.javachangeCommit
a9606ffbforwardsskipsDeserialization()in thesendRequestAsyncanonymous wrapper. PR #21454 threaded this marker through all named wrappers but missed this one. Without it, anyArrowBatchResponseHandlerdispatched under a parent task receives a byte-serialized input and throws atArrowBatchResponse.<init>.Compatibility
With
transport.stream.enabled=false(default), behavior is unchanged.CoordinatorReduceITandCoordinatorReduceMemtableITvalidate the row path.StreamingCoordinatorReduceIT(@LockFeatureFlag(STREAM_TRANSPORT), 2-shard parquet, 20 rows) is the streaming regression gate.Related Issues
Resolves #[Issue number to be closed when this PR is merged]
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.