Skip to content

Commit feae687

Browse files
committed
vortex-parallel-write
Signed-off-by: Mikhail Kot <mikhail@spiraldb.com>
1 parent e727c46 commit feae687

2 files changed

Lines changed: 119 additions & 0 deletions

File tree

vortex-file/src/tests.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,38 @@ async fn write_chunked() {
452452
assert_eq!(array_len, 48);
453453
}
454454

455+
#[tokio::test]
456+
#[cfg_attr(miri, ignore)]
457+
async fn write_ordered() -> VortexResult<()> {
458+
let mut ctx = SESSION.create_execution_ctx();
459+
let dtype = DType::Primitive(I32, Nullability::NonNullable);
460+
461+
let chunks: Vec<VortexResult<(u64, ArrayRef)>> = vec![
462+
Ok((2, buffer![7i32, 8, 9].into_array())),
463+
Ok((0, buffer![1i32, 2, 3].into_array())),
464+
Ok((1, buffer![4i32, 5, 6].into_array())),
465+
];
466+
467+
let mut buf = ByteBufferMut::empty();
468+
SESSION
469+
.write_options()
470+
.write_ordered(&mut buf, dtype, futures::stream::iter(chunks))
471+
.await?;
472+
473+
let array = SESSION
474+
.open_options()
475+
.open_buffer(buf)?
476+
.scan()?
477+
.into_array_stream()?
478+
.read_all()
479+
.await?;
480+
481+
let actual = array.execute::<PrimitiveArray>(&mut ctx)?;
482+
let expected = buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9].into_array();
483+
assert_arrays_eq!(actual, expected, &mut ctx);
484+
Ok(())
485+
}
486+
455487
#[tokio::test]
456488
#[cfg_attr(miri, ignore)]
457489
async fn test_empty_varbin_array_roundtrip() {

vortex-file/src/writer.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4+
use std::collections::BTreeMap;
45
use std::io;
56
use std::io::Write;
7+
use std::pin::Pin;
68
use std::sync::Arc;
79
use std::sync::atomic::AtomicU64;
810
use std::sync::atomic::Ordering;
11+
use std::task::Context;
12+
use std::task::Poll;
913

1014
use futures::FutureExt;
15+
use futures::Stream;
1116
use futures::StreamExt;
1217
use futures::TryStreamExt;
1318
use futures::future::Fuse;
@@ -153,6 +158,25 @@ impl VortexWriteOptions {
153158
.await
154159
}
155160

161+
/// Write a stream of pairs (index, chunk) to a Vortex file, sorting
162+
/// chunks by index before write.
163+
/// "index" must be a contiguous gap-free number starting at 0.
164+
///
165+
/// Errors on duplicate or already written indices.
166+
pub async fn write_ordered<W, S>(
167+
self,
168+
write: W,
169+
dtype: DType,
170+
stream: S,
171+
) -> VortexResult<WriteSummary>
172+
where
173+
W: VortexWrite + Unpin,
174+
S: Stream<Item = VortexResult<(u64, ArrayRef)>> + Unpin + Send + 'static,
175+
{
176+
self.write(write, OrderedArrayStream::new(dtype, stream))
177+
.await
178+
}
179+
156180
async fn write_internal<W: VortexWrite + Unpin>(
157181
self,
158182
mut write: W,
@@ -290,6 +314,69 @@ impl VortexWriteOptions {
290314
}
291315
}
292316

317+
/// Reorder a stream of pairs (index, chunk) into strictly increasing "index".
318+
/// "index" must be contiguous and gap-free starting from 0.
319+
struct OrderedArrayStream<S> {
320+
dtype: DType,
321+
next: u64,
322+
pending: BTreeMap<u64, ArrayRef>,
323+
inner: S,
324+
done: bool,
325+
}
326+
327+
impl<S> OrderedArrayStream<S> {
328+
fn new(dtype: DType, inner: S) -> Self {
329+
Self {
330+
dtype,
331+
next: 0,
332+
pending: BTreeMap::new(),
333+
inner,
334+
done: false,
335+
}
336+
}
337+
}
338+
339+
impl<S> ArrayStream for OrderedArrayStream<S>
340+
where
341+
S: Stream<Item = VortexResult<(u64, ArrayRef)>> + Unpin,
342+
{
343+
fn dtype(&self) -> &DType {
344+
&self.dtype
345+
}
346+
}
347+
348+
impl<S> Stream for OrderedArrayStream<S>
349+
where
350+
S: Stream<Item = VortexResult<(u64, ArrayRef)>> + Unpin,
351+
{
352+
type Item = VortexResult<ArrayRef>;
353+
354+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
355+
let this = self.get_mut();
356+
loop {
357+
if let Some(array) = this.pending.remove(&this.next) {
358+
this.next += 1;
359+
return Poll::Ready(Some(Ok(array)));
360+
}
361+
if this.done {
362+
return Poll::Ready(None);
363+
}
364+
match futures::ready!(Pin::new(&mut this.inner).poll_next(cx)) {
365+
Some(Ok((index, array))) => {
366+
if index < this.next || this.pending.contains_key(&index) {
367+
return Poll::Ready(Some(Err(vortex_err!(
368+
"Duplicate or already present index {index}"
369+
))));
370+
}
371+
this.pending.insert(index, array);
372+
}
373+
Some(Err(e)) => return Poll::Ready(Some(Err(e))),
374+
None => this.done = true,
375+
}
376+
}
377+
}
378+
}
379+
293380
/// An async API for writing Vortex files.
294381
pub struct Writer<'w> {
295382
// The input channel for sending arrays to the writer.

0 commit comments

Comments
 (0)