Skip to content

Commit 5064fd0

Browse files
committed
No eager buffering in network connections
1 parent f1add66 commit 5064fd0

2 files changed

Lines changed: 34 additions & 14 deletions

File tree

src/worker/worker_connection_pool.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use datafusion::execution::memory_pool::{MemoryConsumer, MemoryReservation};
2222
use datafusion::physical_expr_common::metrics::{ExecutionPlanMetricsSet, MetricValue};
2323
use datafusion::physical_plan::metrics::{MetricBuilder, Time};
2424
use futures::stream::BoxStream;
25-
use futures::{Stream, StreamExt, TryFutureExt, TryStreamExt};
25+
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
2626
use http::Extensions;
2727
use pin_project::{pin_project, pinned_drop};
2828
use prost::Message;
@@ -160,6 +160,7 @@ struct RemoteWorkerConnection {
160160
cancel_token: CancellationToken,
161161
per_partition_rx: DashMap<usize, UnboundedReceiver<WorkerMsg>>,
162162

163+
first_poll_notify: Arc<Notify>,
163164
// Signals the demux task that buffered memory has been freed by a consumer.
164165
mem_available_notify: Arc<Notify>,
165166

@@ -246,6 +247,9 @@ impl RemoteWorkerConnection {
246247
let mem_available_notify = Arc::new(Notify::new());
247248
let mem_available_notify_for_task = Arc::clone(&mem_available_notify);
248249

250+
let first_poll_notify = Arc::new(Notify::new());
251+
let first_poll_notify_for_task = Arc::clone(&first_poll_notify);
252+
249253
// Cancellation token allows us to stop the background task promptly when all partition
250254
// streams are dropped (e.g., when the query is cancelled).
251255
let cancel_token = CancellationToken::new();
@@ -255,6 +259,12 @@ impl RemoteWorkerConnection {
255259
// fan them out to the appropriate `per_partition_rx` based on the "partition" declared
256260
// in each individual record batch flight metadata.
257261
let task = SpawnedTask::spawn(async move {
262+
tokio::select! {
263+
biased;
264+
_ = cancel.cancelled() => return,
265+
_ = first_poll_notify_for_task.notified() => {}
266+
}
267+
258268
let mut client = match channel_resolver.get_worker_client_for_url(&url).await {
259269
Ok(v) => v,
260270
Err(err) => {
@@ -364,6 +374,7 @@ impl RemoteWorkerConnection {
364374
not_consumed_streams: Arc::new(AtomicUsize::new(per_partition_rx.len())),
365375
per_partition_rx,
366376
mem_available_notify,
377+
first_poll_notify,
367378

368379
// metrics stuff
369380
memory_reservation: memory_reservation_clone,
@@ -375,14 +386,17 @@ impl RemoteWorkerConnection {
375386
impl WorkerConnection for RemoteWorkerConnection {
376387
/// Streams the provided `partition` from the remote worker.
377388
///
378-
/// Note that this does not issue a network request, the actual network request happened before
379-
/// in the init step, and is in charge of handling not only this `partition`, but also all the
380-
/// partitions passed in `target_partition_range`. This method just streams all the record
381-
/// batches belonging to the provided `partition` from an in-memory queue, but what populates
382-
/// this queue is [WorkerConnection::init].
389+
/// This method does not handle any network connection. Instead, the network comms are delegated
390+
/// to the task spawned by [WorkerConnection::init], who is in charge of polling data not only
391+
/// from the requested `partition`, but from any other partition in `target_partition_range`.
392+
/// This method just streams all the record batches belonging to the provided `partition` from
393+
/// an in-memory queue.
394+
///
395+
/// The task that polls data over the network is held inactive until the first poll to the
396+
/// stream returned by this method.
383397
///
384398
/// When the returned stream is dropped (e.g., due to query cancellation), the background task
385-
/// pulling from the Flight stream will be cancelled promptly.
399+
/// pulling from the Flight stream will be canceled promptly.
386400
fn execute(&self, partition: usize) -> Result<BoxStream<'static, Result<RecordBatch>>> {
387401
let Some((_, partition_receiver)) = self.per_partition_rx.remove(&partition) else {
388402
return internal_err!(
@@ -392,7 +406,13 @@ impl WorkerConnection for RemoteWorkerConnection {
392406
let task = Arc::clone(&self.task);
393407
let cancel_token = self.cancel_token.clone();
394408

395-
let stream = UnboundedReceiverStream::new(partition_receiver);
409+
let first_poll_notify = Arc::clone(&self.first_poll_notify);
410+
let stream = async move {
411+
first_poll_notify.notify_one();
412+
UnboundedReceiverStream::new(partition_receiver)
413+
}
414+
.flatten_stream();
415+
396416
let stream = stream.map_err(|err| FlightError::Tonic(Box::new(err)));
397417
let reservation = Arc::clone(&self.memory_reservation);
398418
let mem_available_notify = Arc::clone(&self.mem_available_notify);

src/worker/worker_service.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use std::time::Duration;
2222
use tonic::codegen::BoxStream;
2323
use tonic::{Request, Response, Status, Streaming};
2424

25+
const TASK_CACHE_TTI: Duration = Duration::from_mins(10);
26+
2527
#[allow(clippy::type_complexity)]
2628
#[derive(Clone, Default)]
2729
pub(super) struct WorkerHooks {
@@ -35,9 +37,9 @@ pub(crate) type TaskDataEntries = Cache<TaskKey, Arc<SingleWriteMultiRead<Result
3537
#[derive(Clone)]
3638
pub struct Worker {
3739
pub(super) runtime: Arc<RuntimeEnv>,
38-
/// TTL-based cache for task execution data. Entries are automatically evicted after 60 seconds.
39-
/// This prevents memory leaks from abandoned or incomplete queries while allowing concurrent
40-
/// access to task results across multiple partition requests.
40+
/// TTL-based cache for task execution data. Entries are automatically evicted after
41+
/// TASK_CACHE_TTI seconds. This prevents memory leaks from abandoned or incomplete queries
42+
/// while allowing concurrent access to task results across multiple partition requests.
4143
pub(super) task_data_entries: Arc<TaskDataEntries>,
4244
pub(super) session_builder: Arc<dyn WorkerSessionBuilder + Send + Sync>,
4345
pub(super) hooks: WorkerHooks,
@@ -47,9 +49,7 @@ pub struct Worker {
4749

4850
impl Default for Worker {
4951
fn default() -> Self {
50-
let cache = Cache::builder()
51-
.time_to_idle(Duration::from_secs(60))
52-
.build();
52+
let cache = Cache::builder().time_to_idle(TASK_CACHE_TTI).build();
5353
Self {
5454
runtime: Arc::new(RuntimeEnv::default()),
5555
task_data_entries: Arc::new(cache),

0 commit comments

Comments
 (0)