Skip to content

Commit 535bf40

Browse files
committed
fix: Surface ingestion errors
- DiskWriter and MemWriter expect and unwrap replaced - New cli env var `P_DATAFUSION_TARGET_PARTITIONS` for controlling number of partitions (default num cpu / 2) - Streaming response uses unbounded channel now
1 parent 138fea3 commit 535bf40

5 files changed

Lines changed: 62 additions & 27 deletions

File tree

src/cli.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818

1919
use clap::{Parser, value_parser};
20-
use std::{env, fs, path::PathBuf};
20+
use std::{env, fs, ops::Div, path::PathBuf};
2121

2222
use url::Url;
2323

@@ -194,6 +194,16 @@ pub struct Options {
194194
)]
195195
pub max_connections: usize,
196196

197+
// DataFusion target partitions
198+
#[arg(
199+
long,
200+
env = "P_DATAFUSION_TARGET_PARTITIONS",
201+
default_value_t = num_cpus::get().div(2).max(1) as u64,
202+
value_parser = value_parser!(u64).range(1..),
203+
help = "Number of partitions for DF to split execution into"
204+
)]
205+
pub target_partitions: u64,
206+
197207
#[arg(
198208
long = "origin",
199209
env = "P_ORIGIN_URI",

src/parseable/staging/writer.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,17 @@ impl<const N: usize> Default for MemWriter<N> {
135135
}
136136

137137
impl<const N: usize> MemWriter<N> {
138-
pub fn push(&mut self, schema_key: &str, rb: &RecordBatch) {
138+
pub fn push(&mut self, schema_key: &str, rb: &RecordBatch) -> Result<(), StagingError> {
139139
if !self.schema_map.contains(schema_key) {
140140
self.schema_map.insert(schema_key.to_owned());
141-
self.schema = Schema::try_merge([self.schema.clone(), (*rb.schema()).clone()]).unwrap();
141+
self.schema = Schema::try_merge([self.schema.clone(), (*rb.schema()).clone()])?;
142142
}
143143

144144
if let Some(record) = self.mutable_buffer.push(rb) {
145-
let record = concat_records(&Arc::new(self.schema.clone()), &record);
145+
let record = concat_records(&Arc::new(self.schema.clone()), &record)?;
146146
self.read_buffer.push(record);
147147
}
148+
Ok(())
148149
}
149150

150151
pub fn clear(&mut self) {
@@ -154,23 +155,29 @@ impl<const N: usize> MemWriter<N> {
154155
self.mutable_buffer.inner.clear();
155156
}
156157

157-
pub fn recordbatch_cloned(&self, schema: &Arc<Schema>) -> Vec<RecordBatch> {
158+
pub fn recordbatch_cloned(
159+
&self,
160+
schema: &Arc<Schema>,
161+
) -> Result<Vec<RecordBatch>, StagingError> {
158162
let mut read_buffer = self.read_buffer.clone();
159163
if !self.mutable_buffer.inner.is_empty() {
160-
let rb = concat_records(schema, &self.mutable_buffer.inner);
164+
let rb = concat_records(schema, &self.mutable_buffer.inner)?;
161165
read_buffer.push(rb)
162166
}
163167

164-
read_buffer
168+
Ok(read_buffer
165169
.into_iter()
166170
.map(|rb| adapt_batch(schema, &rb))
167-
.collect()
171+
.collect())
168172
}
169173
}
170174

171-
fn concat_records(schema: &Arc<Schema>, record: &[RecordBatch]) -> RecordBatch {
175+
fn concat_records(
176+
schema: &Arc<Schema>,
177+
record: &[RecordBatch],
178+
) -> Result<RecordBatch, StagingError> {
172179
let records = record.iter().map(|x| adapt_batch(schema, x)).collect_vec();
173-
concat_batches(schema, records.iter()).unwrap()
180+
Ok(concat_batches(schema, records.iter())?)
174181
}
175182

176183
#[derive(Debug, Default)]

src/parseable/streams.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,16 +186,15 @@ impl Stream {
186186
OBJECT_STORE_DATA_GRANULARITY,
187187
);
188188
let file_path = self.data_path.join(&filename);
189-
let mut writer = DiskWriter::try_new(file_path, &record.schema(), range)
190-
.expect("File and RecordBatch both are checked");
189+
let mut writer = DiskWriter::try_new(file_path, &record.schema(), range)?;
191190

192191
writer.write(record)?;
193192
guard.disk.insert(filename, writer);
194193
}
195194
};
196195
}
197196

198-
guard.mem.push(schema_key, record);
197+
guard.mem.push(schema_key, record)?;
199198

200199
Ok(())
201200
}
@@ -532,7 +531,7 @@ impl Stream {
532531
Ok(())
533532
}
534533

535-
pub fn recordbatches_cloned(&self, schema: &Arc<Schema>) -> Vec<RecordBatch> {
534+
pub fn recordbatches_cloned(&self, schema: &Arc<Schema>) -> Result<Vec<RecordBatch>, StagingError> {
536535
self.writer.lock().unwrap().mem.recordbatch_cloned(schema)
537536
}
538537

src/query/mod.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use datafusion::sql::parser::DFParser;
4343
use datafusion::sql::resolve::resolve_table_references;
4444
use datafusion::sql::sqlparser::dialect::PostgreSqlDialect;
4545
use futures::Stream;
46+
use futures::StreamExt;
4647
use itertools::Itertools;
4748
use once_cell::sync::Lazy;
4849
use serde::{Deserialize, Serialize};
@@ -54,7 +55,7 @@ use std::sync::{Arc, RwLock};
5455
use std::task::{Context, Poll};
5556
use sysinfo::System;
5657
use tokio::runtime::Runtime;
57-
use tokio_stream::wrappers::ReceiverStream;
58+
use tokio_stream::wrappers::UnboundedReceiverStream;
5859
use tracing::Instrument;
5960

6061
use self::error::ExecuteError;
@@ -217,7 +218,8 @@ impl Query {
217218
.with_prefer_existing_sort(true)
218219
//batch size has been made configurable via environment variable
219220
//default value is 20000
220-
.with_batch_size(PARSEABLE.options.execution_batch_size);
221+
.with_batch_size(PARSEABLE.options.execution_batch_size)
222+
.with_target_partitions(PARSEABLE.options.target_partitions as usize);
221223

222224
// Pushdown filters allows DF to push the filters as far down in the plan as possible
223225
// and thus, reducing the number of rows decoded
@@ -226,10 +228,22 @@ impl Query {
226228
// Reorder filters allows DF to decide the order of filters minimizing the cost of filter evaluation
227229
config.options_mut().execution.parquet.reorder_filters = true;
228230
config.options_mut().execution.parquet.binary_as_string = true;
231+
// Bump footer-read hint from the 512 KiB default. Streams with
232+
// many label columns + page-indexed value columns can have
233+
// parquet footers in the 1-2 MiB range; sizing the hint above
234+
// the actual footer collapses the two-read fallback into a
235+
// single GET per file, saving a round trip on every file open.
236+
config.options_mut().execution.parquet.metadata_size_hint = Some(2 * 1024 * 1024);
229237
config
230238
.options_mut()
231239
.execution
232240
.use_row_number_estimates_to_optimize_partitioning = true;
241+
config.options_mut().execution.parquet.enable_page_index = true;
242+
config
243+
.options_mut()
244+
.execution
245+
.parquet
246+
.max_predicate_cache_size = Some(1024 * 1024 * 1024);
233247

234248
//adding this config as it improves query performance as explained here -
235249
// https://github.com/apache/datafusion/pull/13101
@@ -308,23 +322,21 @@ impl Query {
308322
});
309323

310324
let partition_streams = execute_stream_partitioned(plan.clone(), task_ctx.clone())?;
311-
let n = partition_streams.len();
312-
// Bound channel so a slow consumer backpressures producers — caps peak memory.
313-
let (tx, rx) = tokio::sync::mpsc::channel::<
325+
326+
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<
314327
Result<RecordBatch, datafusion::error::DataFusionError>,
315-
>((num_cpus::get() * 4).max(n * 2).max(1));
328+
>();
316329

317-
for s in partition_streams {
330+
for s in partition_streams.into_iter() {
318331
let wrapped =
319332
PartitionedMetricMonitor::new(s, monitor_state.clone(), tenant_id.clone());
320333
let tx = tx.clone();
321334
let span = tracing::Span::current();
322335
tokio::spawn(
323336
async move {
324337
let mut stream: SendableRecordBatchStream = Box::pin(wrapped);
325-
use futures::StreamExt;
326338
while let Some(batch) = stream.next().await {
327-
if tx.send(batch).await.is_err() {
339+
if tx.send(batch).is_err() {
328340
break;
329341
}
330342
}
@@ -334,7 +346,7 @@ impl Query {
334346
}
335347
drop(tx);
336348

337-
let merged = ReceiverStream::new(rx);
349+
let merged = UnboundedReceiverStream::new(rx);
338350
let final_stream = RecordBatchStreamAdapter::new(plan.schema(), merged);
339351
Either::Right(Box::pin(final_stream) as SendableRecordBatchStream)
340352
};

src/query/stream_schema_provider.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,19 @@ impl StandardTableProvider {
150150

151151
// parquet file source, default table parquet options
152152
let file_source = if let Some(phyiscal_expr) = filters {
153-
ParquetSource::new(self.schema.clone()).with_predicate(phyiscal_expr)
153+
ParquetSource::new(self.schema.clone())
154+
.with_predicate(phyiscal_expr)
155+
.with_pushdown_filters(true)
156+
.with_reorder_filters(true)
154157
} else {
155158
ParquetSource::new(self.schema.clone())
159+
.with_pushdown_filters(true)
160+
.with_reorder_filters(true)
156161
};
157162

158163
let mut conf_builder = FileScanConfigBuilder::new(object_store_url, file_source.into())
159164
.with_statistics(statistics)
160-
.with_batch_size(Some(20000))
165+
.with_batch_size(Some(PARSEABLE.options.execution_batch_size))
161166
.with_constraints(Constraints::default())
162167
.with_file_groups(file_groups)
163168
.with_output_ordering(vec![LexOrdering::new([sort_expr]).unwrap()]);
@@ -253,7 +258,9 @@ impl StandardTableProvider {
253258
};
254259

255260
// Staging arrow exection plan
256-
let records = staging.recordbatches_cloned(&self.schema);
261+
let records = staging
262+
.recordbatches_cloned(&self.schema)
263+
.map_err(|e| DataFusionError::Internal(format!("Error during staging exec- {e}")))?;
257264
let arrow_exec = reversed_mem_table(records, self.schema.clone())?
258265
.scan(state, projection, filters, limit)
259266
.await?;

0 commit comments

Comments
 (0)