Skip to content

Commit ab2ac91

Browse files
authored
Replace LayoutStrategy buffered_bytes with a buffered bytes tracker (#8961)
For file writing it's very useful to know how much data is already piped through the writer. We have a CountingWriter but that can only measure flushed bytes. We want to measure the bytes that the writer holds buferred. Instead of recursive buffered_bytes which has numerous problems with stateless strategies and double counting we add a bufferedbytestracker to the layoutwritercontext and let writers report buffered bytes there --------- Signed-off-by: Robert Kruszewski <github@robertk.io> Signed-off-by: "Robert Kruszewski" <robert@spiraldb.com>
1 parent 81c36da commit ab2ac91

27 files changed

Lines changed: 455 additions & 193 deletions

File tree

vortex-array/src/serde.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,12 @@ impl<'a> ArrayNodeFlatBuffer<'a> {
191191
&self,
192192
fbb: &mut FlatBufferBuilder<'fb>,
193193
) -> VortexResult<WIPOffset<fba::ArrayNode<'fb>>> {
194-
let encoding_idx = self
195-
.ctx
196-
.intern(&self.array.encoding_id())
197-
// TODO(ngates): write_flatbuffer should return a result if this can fail.
198-
.ok_or_else(|| {
199-
vortex_err!(
200-
"Array encoding {} not permitted by ctx",
201-
self.array.encoding_id()
202-
)
203-
})?;
194+
let encoding_idx = self.ctx.intern(&self.array.encoding_id()).ok_or_else(|| {
195+
vortex_err!(
196+
"Array encoding {} not permitted by ctx",
197+
self.array.encoding_id()
198+
)
199+
})?;
204200

205201
let metadata_bytes = self.session.array_serialize(self.array)?.ok_or_else(|| {
206202
vortex_err!(

vortex-cuda/src/layout.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use async_trait::async_trait;
1313
use futures::FutureExt;
1414
use futures::StreamExt;
1515
use futures::future::BoxFuture;
16-
use vortex::array::ArrayContext;
1716
use vortex::array::ArrayRef;
1817
use vortex::array::ArrayVTable;
1918
use vortex::array::MaskFuture;
@@ -45,6 +44,7 @@ use vortex::layout::LayoutReader;
4544
use vortex::layout::LayoutReaderRef;
4645
use vortex::layout::LayoutRef;
4746
use vortex::layout::LayoutStrategy;
47+
use vortex::layout::LayoutWriterContext;
4848
use vortex::layout::RowSplits;
4949
use vortex::layout::SplitRange;
5050
use vortex::layout::VTable;
@@ -414,13 +414,12 @@ fn truncate_scalar_stat<F: Fn(Scalar) -> Option<(Scalar, bool)>>(
414414
impl LayoutStrategy for CudaFlatLayoutStrategy {
415415
async fn write_stream(
416416
&self,
417-
ctx: ArrayContext,
417+
ctx: LayoutWriterContext,
418418
segment_sink: SegmentSinkRef,
419419
mut stream: SendableSequentialStream,
420420
_eof: SequencePointer,
421421
session: &VortexSession,
422422
) -> VortexResult<LayoutRef> {
423-
let ctx = ctx.clone();
424423
let options = self.clone();
425424
let Some(chunk) = stream.next().await else {
426425
vortex_bail!("CudaFlatLayoutStrategy needs a single chunk");
@@ -472,7 +471,7 @@ impl LayoutStrategy for CudaFlatLayoutStrategy {
472471
let host_buffers = extract_constant_buffers(&chunk);
473472

474473
let buffers = chunk.serialize(
475-
&ctx,
474+
ctx.array_ctx(),
476475
session,
477476
&SerializeOptions {
478477
offset: 0,
@@ -503,7 +502,7 @@ impl LayoutStrategy for CudaFlatLayoutStrategy {
503502
layout_children(Vec::new()),
504503
CudaFlatData {
505504
segment_id,
506-
ctx: ReadContext::new(ctx.to_ids()),
505+
ctx: ReadContext::new(ctx.array_ctx().to_ids()),
507506
array_tree,
508507
host_buffers: Arc::new(host_buffer_map),
509508
},

vortex-file/src/tests.rs

Lines changed: 149 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use vortex_array::expr::select;
5252
use vortex_array::extension::datetime::TimeUnit;
5353
use vortex_array::extension::datetime::Timestamp;
5454
use vortex_array::extension::datetime::TimestampOptions;
55+
use vortex_array::field_path;
5556
use vortex_array::scalar::Scalar;
5657
use vortex_array::scalar_fn::ScalarFnVTableExt;
5758
use vortex_array::scalar_fn::fns::pack::Pack;
@@ -67,18 +68,25 @@ use vortex_buffer::Buffer;
6768
use vortex_buffer::ByteBuffer;
6869
use vortex_buffer::ByteBufferMut;
6970
use vortex_buffer::buffer;
71+
use vortex_edition::EditionSession;
7072
use vortex_error::VortexExpect;
7173
use vortex_error::VortexResult;
7274
use vortex_flatbuffers::footer as fb;
7375
use vortex_io::session::RuntimeSession;
7476
use vortex_layout::DynLayout;
77+
use vortex_layout::LayoutStrategy;
78+
use vortex_layout::layouts::buffered::BufferedStrategy;
79+
use vortex_layout::layouts::chunked::writer::ChunkedLayoutStrategy;
7580
use vortex_layout::layouts::flat::writer::FlatLayoutStrategy;
81+
use vortex_layout::layouts::struct_::StructStrategy;
82+
use vortex_layout::layouts::table::TableStrategy;
7683
use vortex_layout::layouts::zoned::LegacyStats;
7784
use vortex_layout::layouts::zoned::Zoned;
7885
use vortex_layout::scan::scan_builder::ScanBuilder;
7986
use vortex_layout::scan::split_by::SplitBy;
8087
use vortex_layout::session::LayoutSession;
8188
use vortex_session::VortexSession;
89+
use vortex_zigzag::ZigZag;
8290

8391
use crate::MAX_POSTSCRIPT_SIZE;
8492
use crate::OpenOptionsSessionExt;
@@ -1377,7 +1385,7 @@ async fn test_into_tokio_array_stream() -> VortexResult<()> {
13771385
])
13781386
.into_array();
13791387

1380-
let st = StructArray::from_fields(&[("strings", strings), ("numbers", numbers)]).unwrap();
1388+
let st = StructArray::from_fields(&[("strings", strings), ("numbers", numbers)])?;
13811389
let mut buf = ByteBufferMut::empty();
13821390
SESSION
13831391
.write_options()
@@ -1542,6 +1550,143 @@ async fn test_writer_bytes_written() -> VortexResult<()> {
15421550
Ok(())
15431551
}
15441552

1553+
#[rstest]
1554+
#[case::table_one_leaf(true, 1, false, 32)]
1555+
#[case::table_two_shared_leaves(true, 2, false, 64)]
1556+
#[case::table_field_override(true, 2, true, 64)]
1557+
#[case::struct_default(false, 1, false, 32)]
1558+
#[tokio::test]
1559+
async fn test_writer_buffered_bytes(
1560+
#[case] use_table_strategy: bool,
1561+
#[case] leaf_count: usize,
1562+
#[case] field_override: bool,
1563+
#[case] expected_buffered_bytes: u64,
1564+
) -> VortexResult<()> {
1565+
const BUFFER_SIZE: u64 = 16;
1566+
1567+
let fields = [
1568+
("a", buffer![1u32, 2, 3, 4].into_array()),
1569+
("b", buffer![5u32, 6, 7, 8].into_array()),
1570+
];
1571+
let array = StructArray::from_fields(&fields[..leaf_count])?.into_array();
1572+
1573+
let new_leaf = || -> Arc<dyn LayoutStrategy> {
1574+
Arc::new(BufferedStrategy::new(
1575+
ChunkedLayoutStrategy::new(FlatLayoutStrategy::default()),
1576+
BUFFER_SIZE,
1577+
))
1578+
};
1579+
let validity: Arc<dyn LayoutStrategy> = Arc::new(FlatLayoutStrategy::default());
1580+
let strategy: Arc<dyn LayoutStrategy> = if use_table_strategy {
1581+
let mut table = TableStrategy::new(validity, new_leaf());
1582+
if field_override {
1583+
table = table.with_field_writer(field_path!(b), new_leaf());
1584+
}
1585+
Arc::new(table)
1586+
} else {
1587+
Arc::new(StructStrategy::new(validity, new_leaf()))
1588+
};
1589+
1590+
let mut buf = ByteBufferMut::empty();
1591+
let options = SESSION.write_options().with_strategy(Arc::clone(&strategy));
1592+
let buffered_bytes = options.buffered_bytes_tracker();
1593+
let mut writer = options.writer(&mut buf, array.dtype().clone());
1594+
1595+
assert_eq!(writer.buffered_bytes(), 0);
1596+
1597+
// The third push forces two chunks through the capacity-one input channel while keeping the
1598+
// writer open. Each physical leaf retains two BUFFER_SIZE chunks while peeking for more input.
1599+
writer.push(array.clone()).await?;
1600+
writer.push(array.clone()).await?;
1601+
writer.push(array).await?;
1602+
1603+
assert_eq!(writer.buffered_bytes(), expected_buffered_bytes);
1604+
1605+
let summary = writer.finish().await?;
1606+
assert_eq!(summary.row_count(), 12);
1607+
assert_eq!(buffered_bytes.buffered_bytes(), 0);
1608+
1609+
Ok(())
1610+
}
1611+
1612+
#[tokio::test]
1613+
async fn test_buffered_bytes_are_writer_scoped() -> VortexResult<()> {
1614+
const BUFFER_SIZE: u64 = 16;
1615+
1616+
let array =
1617+
StructArray::from_fields(&[("a", buffer![1u32, 2, 3, 4].into_array())])?.into_array();
1618+
let leaf = Arc::new(BufferedStrategy::new(
1619+
ChunkedLayoutStrategy::new(FlatLayoutStrategy::default()),
1620+
BUFFER_SIZE,
1621+
));
1622+
let strategy: Arc<dyn LayoutStrategy> = Arc::new(TableStrategy::new(
1623+
Arc::new(FlatLayoutStrategy::default()),
1624+
leaf,
1625+
));
1626+
1627+
let mut first_buf = ByteBufferMut::empty();
1628+
let mut first = SESSION
1629+
.write_options()
1630+
.with_strategy(Arc::clone(&strategy))
1631+
.writer(&mut first_buf, array.dtype().clone());
1632+
let mut second_buf = ByteBufferMut::empty();
1633+
let mut second = SESSION
1634+
.write_options()
1635+
.with_strategy(strategy)
1636+
.writer(&mut second_buf, array.dtype().clone());
1637+
1638+
first.push(array.clone()).await?;
1639+
first.push(array.clone()).await?;
1640+
first.push(array.clone()).await?;
1641+
second.push(array.clone()).await?;
1642+
second.push(array.clone()).await?;
1643+
second.push(array).await?;
1644+
1645+
assert_eq!(first.buffered_bytes(), 2 * BUFFER_SIZE);
1646+
assert_eq!(second.buffered_bytes(), 2 * BUFFER_SIZE);
1647+
1648+
first.finish().await?;
1649+
second.finish().await?;
1650+
1651+
Ok(())
1652+
}
1653+
1654+
#[tokio::test]
1655+
async fn test_encoding_registered_after_write_options() -> VortexResult<()> {
1656+
// A session that does not know about ZigZag yet.
1657+
let session = array_session()
1658+
.with::<EditionSession>()
1659+
.with::<LayoutSession>()
1660+
.with::<RuntimeSession>();
1661+
1662+
// Configure the options before the encoding is registered; `write` is what snapshots the
1663+
// session's encodings, so registering in between must still be honoured.
1664+
let options = session
1665+
.write_options()
1666+
.with_strategy(Arc::new(FlatLayoutStrategy::default()));
1667+
vortex_zigzag::initialize(&session);
1668+
crate::enable_all_registered_array_encodings(&session);
1669+
1670+
let array = ZigZag::try_new(buffer![1u32, 2, 3, 4].into_array())?.into_array();
1671+
let dtype = array.dtype().clone();
1672+
1673+
let mut buf = ByteBufferMut::empty();
1674+
options.write(&mut buf, array.to_array_stream()).await?;
1675+
1676+
let chunks: Vec<_> = session
1677+
.open_options()
1678+
.open_buffer(buf)?
1679+
.scan()?
1680+
.into_array_stream()?
1681+
.try_collect()
1682+
.await?;
1683+
let read = ChunkedArray::try_new(chunks, dtype)?.into_array();
1684+
let mut ctx = session.create_execution_ctx();
1685+
assert_arrays_eq!(read, buffer![-1i32, 1, -2, 2].into_array(), &mut ctx);
1686+
1687+
Ok(())
1688+
}
1689+
15451690
#[tokio::test]
15461691
async fn test_writer_empty_chunks() -> VortexResult<()> {
15471692
let mut ctx = SESSION.create_execution_ctx();
@@ -2029,7 +2174,7 @@ async fn timestamp_unit_mismatch_errors_with_constant_children()
20292174
"Expected error from timestamp unit mismatch (ms vs s), but got {} results. \
20302175
This indicates the scanner silently applied the filter incorrectly when \
20312176
DateTimePartsArray children use ConstantArray encoding.",
2032-
results.unwrap().len()
2177+
results?.len()
20332178
);
20342179

20352180
Ok(())
@@ -2223,7 +2368,7 @@ async fn test_segment_ordering_dict_codes_before_values() -> VortexResult<()> {
22232368
let strings = VarBinArray::from(values).into_array();
22242369
let numbers = PrimitiveArray::from_iter(0..n as i32).into_array();
22252370

2226-
let st = StructArray::from_fields(&[("strings", strings), ("numbers", numbers)]).unwrap();
2371+
let st = StructArray::from_fields(&[("strings", strings), ("numbers", numbers)])?;
22272372

22282373
let mut buf = ByteBufferMut::empty();
22292374
let summary = SESSION
@@ -2345,8 +2490,7 @@ async fn test_segment_ordering_zonemaps_after_data() -> VortexResult<()> {
23452490
("strings", strings),
23462491
("numbers", numbers),
23472492
("floats", floats),
2348-
])
2349-
.unwrap();
2493+
])?;
23502494

23512495
let mut buf = ByteBufferMut::empty();
23522496
let summary = SESSION

vortex-file/src/v2/file_stats_reader.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ mod tests {
246246
);
247247
let layout = strategy
248248
.write_stream(
249-
ctx,
249+
ctx.into(),
250250
Arc::<TestSegments>::clone(&segments),
251251
struct_array.into_array().to_array_stream().sequenced(ptr),
252252
eof,
@@ -285,7 +285,7 @@ mod tests {
285285
);
286286
let layout = strategy
287287
.write_stream(
288-
ctx,
288+
ctx.into(),
289289
Arc::<TestSegments>::clone(&segments),
290290
struct_array.into_array().to_array_stream().sequenced(ptr),
291291
eof,
@@ -324,7 +324,7 @@ mod tests {
324324
);
325325
let layout = strategy
326326
.write_stream(
327-
ctx,
327+
ctx.into(),
328328
Arc::<TestSegments>::clone(&segments),
329329
struct_array.into_array().to_array_stream().sequenced(ptr),
330330
eof,
@@ -373,7 +373,7 @@ mod tests {
373373
);
374374
let layout = strategy
375375
.write_stream(
376-
ctx,
376+
ctx.into(),
377377
Arc::clone(&segments) as Arc<dyn SegmentSink>,
378378
struct_array.into_array().to_array_stream().sequenced(ptr),
379379
eof,
@@ -422,7 +422,7 @@ mod tests {
422422
);
423423
let layout = strategy
424424
.write_stream(
425-
ctx,
425+
ctx.into(),
426426
Arc::clone(&segments) as Arc<dyn SegmentSink>,
427427
struct_array.into_array().to_array_stream().sequenced(ptr),
428428
eof,

0 commit comments

Comments
 (0)