vortex_layout/layouts/zoned/
writer.rs1use std::num::NonZeroUsize;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use futures::StreamExt as _;
11use parking_lot::Mutex;
12use vortex_array::ArrayContext;
13use vortex_array::IntoArray;
14use vortex_array::VortexSessionExecute;
15use vortex_array::aggregate_fn::AggregateFnRef;
16use vortex_array::aggregate_fn::AggregateFnVTable;
17use vortex_array::aggregate_fn::AggregateFnVTableExt;
18use vortex_array::aggregate_fn::EmptyOptions;
19use vortex_array::aggregate_fn::NumericalAggregateOpts;
20use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
21use vortex_array::aggregate_fn::fns::bounded_max::BoundedMaxOptions;
22use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
23use vortex_array::aggregate_fn::fns::bounded_min::BoundedMinOptions;
24use vortex_array::aggregate_fn::fns::max::Max;
25use vortex_array::aggregate_fn::fns::min::Min;
26use vortex_array::aggregate_fn::fns::nan_count::NanCount;
27use vortex_array::aggregate_fn::fns::null_count::NullCount;
28use vortex_array::aggregate_fn::fns::sum::Sum;
29use vortex_array::aggregate_fn::session::AggregateFnSessionExt;
30use vortex_array::dtype::DType;
31use vortex_error::VortexError;
32use vortex_error::VortexResult;
33use vortex_io::session::RuntimeSessionExt;
34use vortex_session::VortexSession;
35use vortex_utils::parallelism::get_available_parallelism;
36
37use crate::IntoLayout;
38use crate::LayoutRef;
39use crate::LayoutStrategy;
40use crate::layouts::zoned::AggregateStatsAccumulator;
41use crate::layouts::zoned::ZonedLayout;
42use crate::layouts::zoned::aggregate_partials;
43use crate::layouts::zoned::schema::default_bounded_stat_max_bytes;
44use crate::segments::SegmentSinkRef;
45use crate::sequence::SendableSequentialStream;
46use crate::sequence::SequencePointer;
47use crate::sequence::SequentialArrayStreamExt;
48use crate::sequence::SequentialStreamAdapter;
49use crate::sequence::SequentialStreamExt;
50
51pub struct ZonedLayoutOptions {
56 pub block_size: NonZeroUsize,
58 pub aggregate_fns: Option<Arc<[AggregateFnRef]>>,
62 pub concurrency: NonZeroUsize,
64}
65
66impl Default for ZonedLayoutOptions {
67 fn default() -> Self {
68 Self {
69 block_size: unsafe { NonZeroUsize::new_unchecked(8192) },
70 aggregate_fns: None,
71 concurrency: unsafe {
72 NonZeroUsize::new_unchecked(get_available_parallelism().unwrap_or(1))
73 },
74 }
75 }
76}
77
78pub struct ZonedStrategy {
79 child: Arc<dyn LayoutStrategy>,
80 stats: Arc<dyn LayoutStrategy>,
81 options: ZonedLayoutOptions,
82}
83
84impl ZonedStrategy {
85 pub fn new<Child: LayoutStrategy, Stats: LayoutStrategy>(
87 child: Child,
88 stats: Stats,
89 options: ZonedLayoutOptions,
90 ) -> Self {
91 Self {
92 child: Arc::new(child),
93 stats: Arc::new(stats),
94 options,
95 }
96 }
97}
98
99#[async_trait]
100impl LayoutStrategy for ZonedStrategy {
101 async fn write_stream(
102 &self,
103 ctx: ArrayContext,
104 segment_sink: SegmentSinkRef,
105 stream: SendableSequentialStream,
106 mut eof: SequencePointer,
107 session: &VortexSession,
108 ) -> VortexResult<LayoutRef> {
109 let aggregate_fns = self
110 .options
111 .aggregate_fns
112 .clone()
113 .unwrap_or_else(|| default_zoned_aggregate_fns(stream.dtype(), session));
114 let compute_session = session.clone();
115
116 let stats_accumulator = Arc::new(Mutex::new(AggregateStatsAccumulator::new(
117 stream.dtype(),
118 &aggregate_fns,
119 )));
120 let aggregate_fns = stats_accumulator.lock().aggregate_fns();
121
122 let stream_dtype = stream.dtype().clone();
123 let concurrency = self.options.concurrency.get();
124 let stream = stream
125 .map(move |item| {
126 let aggregate_fns = Arc::clone(&aggregate_fns);
127 let session = compute_session.clone();
128 session.handle().spawn_cpu(move || {
129 let (sequence_id, chunk) = item?;
130 let partials = aggregate_partials(
131 &chunk,
132 &aggregate_fns,
133 &mut session.create_execution_ctx(),
134 )?;
135 Ok::<_, VortexError>((sequence_id, chunk, partials))
136 })
137 })
138 .buffered(concurrency);
139
140 let stats_accumulator2 = Arc::clone(&stats_accumulator);
143 let stream = SequentialStreamAdapter::new(
144 stream_dtype,
145 stream.map(move |item| {
146 let (sequence_id, chunk, partials) = item?;
147 stats_accumulator2.lock().push_partials(partials)?;
148 Ok((sequence_id, chunk))
149 }),
150 )
151 .sendable();
152
153 let block_size = self.options.block_size;
154
155 let data_eof = eof.split_off();
157 let data_layout = self
158 .child
159 .write_stream(
160 ctx.clone(),
161 Arc::clone(&segment_sink),
162 stream,
163 data_eof,
164 session,
165 )
166 .await?;
167
168 let mut exec_ctx = session.create_execution_ctx();
169 let Some((stats_array, aggregate_fns)) =
170 stats_accumulator.lock().as_array(&mut exec_ctx)?
171 else {
172 return Ok(data_layout);
175 };
176
177 let stats_stream = stats_array
180 .into_array()
181 .to_array_stream()
182 .sequenced(eof.split_off());
183 let zones_layout = self
184 .stats
185 .write_stream(ctx, Arc::clone(&segment_sink), stats_stream, eof, session)
186 .await?;
187
188 Ok(
189 ZonedLayout::try_new(data_layout, zones_layout, block_size, aggregate_fns)?
190 .into_layout(),
191 )
192 }
193
194 fn buffered_bytes(&self) -> u64 {
195 self.child.buffered_bytes() + self.stats.buffered_bytes()
196 }
197}
198
199fn default_zoned_aggregate_fns(dtype: &DType, session: &VortexSession) -> Arc<[AggregateFnRef]> {
200 let (max, min) = match dtype {
201 DType::Utf8(_) | DType::Binary(_) => (
202 BoundedMax.bind(BoundedMaxOptions {
203 max_bytes: default_bounded_stat_max_bytes(),
204 }),
205 BoundedMin.bind(BoundedMinOptions {
206 max_bytes: default_bounded_stat_max_bytes(),
207 }),
208 ),
209 _ => (
210 Max.bind(NumericalAggregateOpts::skip_nans()),
211 Min.bind(NumericalAggregateOpts::skip_nans()),
212 ),
213 };
214
215 let mut aggregate_fns = vec![max, min];
216 if Sum
217 .return_dtype(&NumericalAggregateOpts::skip_nans(), dtype)
218 .is_some()
219 {
220 aggregate_fns.push(Sum.bind(NumericalAggregateOpts::skip_nans()));
221 }
222 aggregate_fns.push(NanCount.bind(EmptyOptions));
223 aggregate_fns.push(NullCount.bind(EmptyOptions));
224
225 aggregate_fns.extend(session.aggregate_fns().zone_stat_defaults(dtype));
227
228 aggregate_fns.into()
229}
230
231#[cfg(test)]
232mod tests {
233 use vortex_array::aggregate_fn::fns::bounded_max::BoundedMax;
234 use vortex_array::aggregate_fn::fns::bounded_min::BoundedMin;
235 use vortex_array::aggregate_fn::fns::max::Max;
236 use vortex_array::aggregate_fn::fns::min::Min;
237 use vortex_array::aggregate_fn::fns::sum::Sum;
238 use vortex_array::dtype::Nullability;
239 use vortex_array::dtype::PType;
240 use vortex_array::extension::datetime::TimeUnit;
241 use vortex_array::extension::datetime::Timestamp;
242
243 use super::*;
244
245 #[test]
246 fn default_aggregates_bound_variable_length_min_max() {
247 let aggregate_fns = default_zoned_aggregate_fns(
248 &DType::Utf8(Nullability::NonNullable),
249 &vortex_array::array_session(),
250 );
251
252 assert_eq!(
253 aggregate_fns[0].as_::<BoundedMax>().max_bytes,
254 default_bounded_stat_max_bytes()
255 );
256 assert_eq!(
257 aggregate_fns[1].as_::<BoundedMin>().max_bytes,
258 default_bounded_stat_max_bytes()
259 );
260 }
261
262 #[test]
263 fn default_aggregates_keep_fixed_width_min_max_exact() {
264 let aggregate_fns =
265 default_zoned_aggregate_fns(&PType::I32.into(), &vortex_array::array_session());
266
267 assert!(aggregate_fns[0].is::<Max>());
268 assert!(aggregate_fns[1].is::<Min>());
269 assert!(aggregate_fns[2].is::<Sum>());
270 }
271
272 #[test]
273 fn default_aggregates_skip_sum_for_non_summable_dtype() {
274 let dtype = DType::Extension(
275 Timestamp::new(TimeUnit::Microseconds, Nullability::Nullable).erased(),
276 );
277 let aggregate_fns = default_zoned_aggregate_fns(&dtype, &vortex_array::array_session());
278
279 assert!(
280 aggregate_fns
281 .iter()
282 .all(|aggregate_fn| !aggregate_fn.is::<Sum>())
283 );
284 }
285}