[Arrow Flight RPC] Add metadata channel on ArrowBatchResponse - #22003
Conversation
PR Reviewer Guide 🔍(Review updated until commit b070add)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to b070add Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit a50a09e
Suggestions up to commit b10e5ba
Suggestions up to commit 444d754
|
|
❌ Gradle check result for 444d754: 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? |
444d754 to
b10e5ba
Compare
|
Persistent review updated to latest commit b10e5ba |
f3c052a to
a50a09e
Compare
|
Persistent review updated to latest commit a50a09e |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #22003 +/- ##
============================================
- Coverage 73.47% 73.46% -0.01%
+ Complexity 75588 75567 -21
============================================
Files 6037 6038 +1
Lines 342786 342811 +25
Branches 49311 49312 +1
============================================
- Hits 251859 251857 -2
- Misses 70924 70929 +5
- Partials 20003 20025 +22 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Surfaces Arrow Flight's per-frame metadata via ArrowBatchResponse so actions can attach opaque bytes to a batch and read them on the consumer without any transport-SPI changes. Wire path: putNext(ArrowBuf) on send, getLatestMetadata() on receive (both already in Arrow Flight 18.x). The transport copies bytes off the wire on the receive side so the byte[] outlives the stream cursor. API surface: - ArrowBatchResponse(VectorSchemaRoot, byte[] metadata) — send-side ctor. - ArrowBatchResponse#getMetadata() — receive-side accessor. - ArrowStreamInput#getMetadata() — default null; NativeArrow input carries it through. Transport plumbing: - FlightServerChannel.sendBatch(header, output, byte[]) — when non-null, allocates an ArrowBuf from the channel allocator and uses putNext(buf). - FlightOutboundHandler.processBatchTask reads ArrowBatchResponse.getMetadata() and threads it through. - FlightTransportResponse.nextResponse pulls flightStream.getLatestMetadata() per frame, copies into byte[]. No changes to public SPI in server/. No FlightTransportChannel surface changes. Native Arrow path only — byte-serialized path unchanged. Tests: - StreamMetadataIT — two cases (profile=true: metadata observed once on last batch with bytes intact; profile=false: zero metadata observations). - FlightOutboundHandlerTests updated for new 3-arg sendBatch signature. - NativeArrowTransportIT + all unit tests still pass. Docs: native-arrow-transport-design.md gains an "Application Metadata" section; server-side-streaming-guide.md cross-links to it. Signed-off-by: Rishabh Maurya <rishabhmaurya05@gmail.com>
a50a09e to
b070add
Compare
|
Persistent review updated to latest commit b070add |
|
❌ Gradle check result for b070add: 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? |
|
❌ Gradle check result for b070add: 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? |
|
❌ Gradle check result for b070add: 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? |
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in #22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
…arch-project#22003) Surfaces Arrow Flight's per-frame metadata via ArrowBatchResponse so actions can attach opaque bytes to a batch and read them on the consumer without any transport-SPI changes. Wire path: putNext(ArrowBuf) on send, getLatestMetadata() on receive (both already in Arrow Flight 18.x). The transport copies bytes off the wire on the receive side so the byte[] outlives the stream cursor. API surface: - ArrowBatchResponse(VectorSchemaRoot, byte[] metadata) — send-side ctor. - ArrowBatchResponse#getMetadata() — receive-side accessor. - ArrowStreamInput#getMetadata() — default null; NativeArrow input carries it through. Transport plumbing: - FlightServerChannel.sendBatch(header, output, byte[]) — when non-null, allocates an ArrowBuf from the channel allocator and uses putNext(buf). - FlightOutboundHandler.processBatchTask reads ArrowBatchResponse.getMetadata() and threads it through. - FlightTransportResponse.nextResponse pulls flightStream.getLatestMetadata() per frame, copies into byte[]. No changes to public SPI in server/. No FlightTransportChannel surface changes. Native Arrow path only — byte-serialized path unchanged. Tests: - StreamMetadataIT — two cases (profile=true: metadata observed once on last batch with bytes intact; profile=false: zero metadata observations). - FlightOutboundHandlerTests updated for new 3-arg sendBatch signature. - NativeArrowTransportIT + all unit tests still pass. Docs: native-arrow-transport-design.md gains an "Application Metadata" section; server-side-streaming-guide.md cross-links to it. Signed-off-by: Rishabh Maurya <rishabhmaurya05@gmail.com>
…search-project#21972) Extends the profile API to return per-shard DataFusion execution metrics (output_rows, elapsed_compute, scan_time, row_groups_pruned, etc.) in the profile output as 'data_node_metrics' per task. Uses the ArrowBatchResponse metadata channel (merged in opensearch-project#22003) to transmit metrics in-band on the last data batch. No sentinel frames, no transport SPI changes, no threading races. Data node side: - AnalyticsSearchService extracts metrics after stream exhaustion - channelResponseHandler buffers one batch ahead; attaches metrics to the final batch via FragmentExecutionArrowResponse(root, metadata) Coordinator side: - handleStreamResponse reads last.getMetadata() after the stream loop - StreamingResponseListener.onStreamComplete(bytes) passes to task - ShardFragmentStageExecution stores on StageTask.setDataNodeMetrics - QueryProfileBuilder parses JSON into TaskProfile.dataNodeMetrics Also includes: - Profile flag propagation (QueryContext → FragmentExecutionRequest) - Rust FFM: df_stream_get_metrics extracts ExecutionPlan.metrics() - Debug logging of metrics at shard level (guarded by isDebugEnabled) - Integration test: ExplainApiIT.testExplainTasksHaveDataNodeMetrics Signed-off-by: Finn Carroll <carrofin@amazon.com>
Summary
Adds an opaque
byte[]metadata channel onArrowBatchResponseso a stream-transport action can attach bytes to a batch (or to its terminal batch) and read them back on the consumer. Wire path is Arrow Flight'sputNext(ArrowBuf)/getLatestMetadata(). No changes to the public stream-transport SPI inserver/.This is a self-contained prototype. It's published as a draft so the use case in #21972 (DataFusion operator metrics → coordinator profile output) can borrow from it if helpful.
Design rationale
The stream-transport tenets in #21253 and #21465 are: the transport moves
TransportResponseinstances; the action defines the response shape. Anything an action needs to send between nodes belongs on a request or response object. The transport stays narrow and is shared across actions.Putting the metadata bytes on
ArrowBatchResponsekeeps the abstraction faithful to that:server/transport/stream/StreamTransportResponsedoesn't grow new methods — every transport implementation that exists or will exist remains decoupled from this feature.FlightTransportChanneldoesn't grow new methods either — plugins talk to it through the samesendResponseBatchthey already use.putNext(ArrowBuf)), not a custom convention layered on top.The receive-side flow is symmetrical:
FlightTransportResponsecopiesgetLatestMetadata()into abyte[]on each frame (Flight retains buffer ownership otherwise), and the response constructor pulls it viaArrowStreamInput.getMetadata().The mechanism covers both shapes naturally: per-batch metadata (row offsets, watermarks, batch-level stats) and stream-terminal metadata (attach to the last batch — profile counters, summary stats).
Integrating the analytics-engine profile flow (#21972)
The Rust FFM additions (
df_stream_get_metrics,df_free_metrics_buf), theprofileflag onQueryContext/FragmentExecutionRequest, and theTaskProfile.dataNodeMetricsparsing land as-is. Only the transport hop changes.Producer (data node,
AnalyticsSearchService.executeFragmentStreamingAsync): attach the metrics blob to the last batch's response.This drops the
onCompleteWithMetricscallback, the reflection-basedsendStreamMetadatacall inAnalyticsSearchTransportService, and thependingStreamMetadatafield onFlightTransportChannel.Consumer (coordinator,
AnalyticsSearchTransportService.handleStreamResponse): read inside the existing batch loop.This drops the
getTrailingMetadata()call after the loop and the corresponding addition toStreamTransportResponse.Empty-shard case: when no batches are produced, send a single
FragmentExecutionArrowResponse(emptyRoot, metricsBytes)— the metadata rides on a real (zero-row but legitimately-shaped) batch, no sentinel convention needed.Tests
StreamMetadataIT— profile=true: metadata observed once on the last batch, bytes intact end-to-end. profile=false: zero observations, data path identical.FlightOutboundHandlerTestsupdated for the newsendBatchsignature.NativeArrowTransportITpass.Test plan
./gradlew :plugins:arrow-base:check :plugins:arrow-flight-rpc:check./gradlew :plugins:arrow-flight-rpc:internalClusterTest --tests "*StreamMetadataIT" --tests "*NativeArrowTransportIT"Related