risingwave_stream/executor/over_window/
eowc.rs1use std::collections::HashMap;
16use std::marker::PhantomData;
17use std::ops::Bound;
18
19use anyhow::Context;
20use itertools::Itertools;
21use risingwave_common::array::stream_record::Record;
22use risingwave_common::array::{ArrayRef, Op};
23use risingwave_common::row::RowExt;
24use risingwave_common::types::{ToDatumRef, ToOwnedDatum};
25use risingwave_common::util::iter_util::{ZipEqDebug, ZipEqFast};
26use risingwave_common::util::memcmp_encoding::{self, MemcmpEncoded};
27use risingwave_common::util::row_serde::OrderedRowSerde;
28use risingwave_common::util::sort_util::OrderType;
29use risingwave_common::{must_match, row};
30use risingwave_common_estimate_size::EstimateSize;
31use risingwave_common_estimate_size::collections::EstimatedVecDeque;
32use risingwave_expr::window_function::{
33 StateEvictHint, StateKey, WindowFuncCall, WindowStateSnapshot, WindowStates,
34 create_window_state,
35};
36use risingwave_pb::window_function::{
37 StateKey as PbStateKey, WindowStateSnapshot as PbWindowStateSnapshot,
38};
39use risingwave_storage::store::PrefetchOptions;
40use tracing::debug;
41
42use crate::cache::ManagedLruCache;
43use crate::common::metrics::MetricsInfo;
44use crate::executor::prelude::*;
45
46struct Partition {
47 states: WindowStates,
48 curr_row_buffer: EstimatedVecDeque<OwnedRow>,
49 intermediate_state_row: Option<OwnedRow>,
52}
53
54impl EstimateSize for Partition {
55 fn estimated_heap_size(&self) -> usize {
56 let mut total_size = self.curr_row_buffer.estimated_heap_size();
57 for state in self.states.iter() {
58 total_size += state.estimated_heap_size();
59 }
60 if let Some(row) = &self.intermediate_state_row {
61 total_size += row.estimated_heap_size();
62 }
63 total_size
64 }
65}
66
67type PartitionCache = ManagedLruCache<MemcmpEncoded, Partition>; fn encode_snapshot(snapshot: &WindowStateSnapshot, pk_ser: &OrderedRowSerde) -> Vec<u8> {
71 use prost::Message;
72 let pb = PbWindowStateSnapshot {
73 last_output_key: snapshot.last_output_key.as_ref().map(|key| PbStateKey {
74 order_key: key.order_key.to_vec(),
75 pk: key.pk.as_inner().memcmp_serialize(pk_ser),
76 }),
77 function_state: Some(snapshot.function_state.clone()),
78 };
79 pb.encode_to_vec()
80}
81
82fn decode_snapshot(
84 bytes: &[u8],
85 pk_deser: &OrderedRowSerde,
86) -> StreamExecutorResult<WindowStateSnapshot> {
87 use prost::Message;
88 let pb = PbWindowStateSnapshot::decode(bytes).context("failed to decode snapshot")?;
89 let last_output_key = pb
90 .last_output_key
91 .map(|key| {
92 let pk = pk_deser
93 .deserialize(&key.pk)
94 .context("failed to deserialize pk")?;
95 Ok::<_, anyhow::Error>(StateKey {
96 order_key: key.order_key.into(),
97 pk: pk.into(),
98 })
99 })
100 .transpose()?;
101 let function_state = pb
102 .function_state
103 .context("snapshot missing function_state")?;
104 Ok(WindowStateSnapshot {
105 last_output_key,
106 function_state,
107 })
108}
109
110pub struct EowcOverWindowExecutor<S: StateStore> {
144 input: Executor,
145 inner: ExecutorInner<S>,
146}
147
148struct ExecutorInner<S: StateStore> {
149 actor_ctx: ActorContextRef,
150
151 schema: Schema,
152 calls: Vec<WindowFuncCall>,
153 input_stream_key: Vec<usize>,
154 partition_key_indices: Vec<usize>,
155 order_key_index: usize, state_table: StateTable<S>,
157 evict_row_indices: Vec<usize>,
160 watermark_sequence: AtomicU64Ref,
161 intermediate_state_table: Option<StateTable<S>>,
164 pk_serde: Option<OrderedRowSerde>,
167}
168
169struct ExecutionVars<S: StateStore> {
170 partitions: PartitionCache,
171 _phantom: PhantomData<S>,
172}
173
174impl<S: StateStore> Execute for EowcOverWindowExecutor<S> {
175 fn execute(self: Box<Self>) -> BoxedMessageStream {
176 self.executor_inner().boxed()
177 }
178}
179
180pub struct EowcOverWindowExecutorArgs<S: StateStore> {
181 pub actor_ctx: ActorContextRef,
182
183 pub input: Executor,
184
185 pub schema: Schema,
186 pub calls: Vec<WindowFuncCall>,
187 pub partition_key_indices: Vec<usize>,
188 pub order_key_index: usize,
189 pub state_table: StateTable<S>,
190 pub watermark_epoch: AtomicU64Ref,
191 pub intermediate_state_table: Option<StateTable<S>>,
194}
195
196impl<S: StateStore> EowcOverWindowExecutor<S> {
197 pub fn new(args: EowcOverWindowExecutorArgs<S>) -> Self {
198 let input_info = args.input.info().clone();
199
200 debug_assert!(
202 args.state_table.pk_indices().iter().all(|i| {
203 args.partition_key_indices.contains(i)
204 || *i == args.order_key_index
205 || input_info.stream_key.contains(i)
206 }),
207 "state table pk must consist of partition key, order key and input stream key columns"
208 );
209 let num_partition_key_cols = args.partition_key_indices.len();
210 let evict_row_indices = (0..input_info.schema.len())
211 .map(|i| {
212 if let Some(j) = args.partition_key_indices.iter().position(|&k| k == i) {
213 j
214 } else if i == args.order_key_index {
215 num_partition_key_cols
216 } else if let Some(j) = input_info.stream_key.iter().position(|&k| k == i) {
217 num_partition_key_cols + 1 + j
218 } else {
219 num_partition_key_cols + 1 + input_info.stream_key.len()
221 }
222 })
223 .collect_vec();
224
225 let pk_serde = args.intermediate_state_table.as_ref().map(|_| {
227 let pk_data_types: Vec<_> = input_info
228 .stream_key
229 .iter()
230 .map(|&i| args.schema[i].data_type())
231 .collect();
232 let pk_order_types: Vec<_> = input_info
233 .stream_key
234 .iter()
235 .map(|_| OrderType::ascending())
236 .collect();
237 OrderedRowSerde::new(pk_data_types, pk_order_types)
238 });
239
240 Self {
241 input: args.input,
242 inner: ExecutorInner {
243 actor_ctx: args.actor_ctx,
244 schema: args.schema,
245 calls: args.calls,
246 input_stream_key: input_info.stream_key,
247 partition_key_indices: args.partition_key_indices,
248 order_key_index: args.order_key_index,
249 state_table: args.state_table,
250 evict_row_indices,
251 watermark_sequence: args.watermark_epoch,
252 intermediate_state_table: args.intermediate_state_table,
253 pk_serde,
254 },
255 }
256 }
257
258 async fn load_intermediate_state(
260 this: &ExecutorInner<S>,
261 partition: &mut Partition,
262 partition_key: impl Row,
263 encoded_partition_key: &MemcmpEncoded,
264 ) -> StreamExecutorResult<()> {
265 let Some(intermediate_state_table) = &this.intermediate_state_table else {
266 return Ok(());
267 };
268 let pk_serde = this
269 .pk_serde
270 .as_ref()
271 .expect("pk_serde must be set when intermediate_state_table is present");
272
273 for state in partition.states.iter_mut() {
274 state.enable_persistence();
275 }
276
277 let partition_key_owned = partition_key.to_owned_row();
278 if let Some(row) = intermediate_state_table
279 .get_row(&partition_key_owned)
280 .await?
281 {
282 let num_partition_key_cols = this.partition_key_indices.len();
283 let num_calls = this.calls.len();
284
285 for call_index in 0..num_calls {
286 let state_col = num_partition_key_cols + call_index;
287 if state_col < row.len() {
288 if let Some(state_bytes) = row.datum_at(state_col) {
289 let snapshot = decode_snapshot(state_bytes.into_bytea(), pk_serde)?;
290 debug!(
291 "Restoring intermediate state for partition {:?}, call_index {}, has_last_key: {}",
292 encoded_partition_key,
293 call_index,
294 snapshot.last_output_key.is_some()
295 );
296 partition
297 .states
298 .get_mut(call_index)
299 .unwrap()
300 .restore(snapshot)?;
301 }
302 } else {
303 return Err(anyhow::anyhow!(
304 "intermediate state row has fewer columns ({}) than expected ({}) \
305 at call_index {}, state may be corrupted",
306 row.len(),
307 num_partition_key_cols + num_calls,
308 call_index
309 )
310 .into());
311 }
312 }
313 partition.intermediate_state_row = Some(row);
314 }
315 Ok(())
316 }
317
318 fn persist_intermediate_state(
320 this: &mut ExecutorInner<S>,
321 partition: &mut Partition,
322 partition_key: impl Row,
323 ) {
324 let Some(intermediate_state_table) = &mut this.intermediate_state_table else {
325 return;
326 };
327 let pk_serde = this
328 .pk_serde
329 .as_ref()
330 .expect("pk_serde must be set when intermediate_state_table is present");
331
332 let num_calls = partition.states.len();
333 let num_partition_key_cols = partition_key.len();
334
335 let mut new_row_values = Vec::with_capacity(num_partition_key_cols + num_calls);
337 for datum in partition_key.iter() {
338 new_row_values.push(datum.to_owned_datum());
339 }
340
341 for (call_index, state) in partition.states.iter().enumerate() {
343 if let Some(snapshot) = state.snapshot() {
344 let snapshot_bytes = encode_snapshot(&snapshot, pk_serde);
345 new_row_values.push(Some(snapshot_bytes.into_boxed_slice().into()));
346 } else if let Some(ref old_row) = partition.intermediate_state_row {
347 let state_col = num_partition_key_cols + call_index;
348 if state_col < old_row.len() {
349 new_row_values.push(old_row.datum_at(state_col).to_owned_datum());
350 } else {
351 new_row_values.push(None);
352 }
353 } else {
354 new_row_values.push(None);
355 }
356 }
357 let new_row = OwnedRow::new(new_row_values);
358
359 if let Some(old_row) = partition.intermediate_state_row.take() {
361 intermediate_state_table.update(old_row, new_row.clone());
362 } else {
363 intermediate_state_table.insert(new_row.clone());
364 }
365 partition.intermediate_state_row = Some(new_row);
366 }
367
368 async fn ensure_key_in_cache(
369 this: &ExecutorInner<S>,
370 cache: &mut PartitionCache,
371 partition_key: impl Row,
372 encoded_partition_key: &MemcmpEncoded,
373 ) -> StreamExecutorResult<()> {
374 if cache.contains(encoded_partition_key) {
375 return Ok(());
376 }
377
378 let mut partition = Partition {
379 states: WindowStates::new(this.calls.iter().map(create_window_state).try_collect()?),
380 curr_row_buffer: Default::default(),
381 intermediate_state_row: None,
382 };
383
384 Self::load_intermediate_state(this, &mut partition, &partition_key, encoded_partition_key)
386 .await?;
387
388 let sub_range: &(Bound<OwnedRow>, Bound<OwnedRow>) = &(Bound::Unbounded, Bound::Unbounded);
389 let table_iter = this
391 .state_table
392 .iter_with_prefix(&partition_key, sub_range, PrefetchOptions::default())
393 .await?;
394
395 #[for_await]
396 for keyed_row in table_iter {
397 let row = keyed_row?.into_owned_row();
398 let order_key_enc = memcmp_encoding::encode_row(
399 row::once(Some(
400 row.datum_at(this.order_key_index)
401 .expect("order key column must be non-NULL")
402 .into_scalar_impl(),
403 )),
404 &[OrderType::ascending()],
405 )?;
406 let pk = (&row).project(&this.input_stream_key).into_owned_row();
407 let key = StateKey {
408 order_key: order_key_enc,
409 pk: pk.into(),
410 };
411 for (call, state) in this.calls.iter().zip_eq_fast(partition.states.iter_mut()) {
412 state.append(
413 key.clone(),
414 (&row)
415 .project(call.args.val_indices())
416 .into_owned_row()
417 .as_inner()
418 .into(),
419 );
420 }
421 partition.curr_row_buffer.push_back(row);
422 }
423
424 assert!(partition.states.are_aligned());
426
427 while partition.states.are_ready() {
430 partition.states.just_slide()?;
431 partition.curr_row_buffer.pop_front();
432 }
433
434 cache.put(encoded_partition_key.clone(), partition);
435 Ok(())
436 }
437
438 async fn apply_chunk(
439 this: &mut ExecutorInner<S>,
440 vars: &mut ExecutionVars<S>,
441 chunk: StreamChunk,
442 ) -> StreamExecutorResult<Option<StreamChunk>> {
443 let mut builders = this.schema.create_array_builders(chunk.capacity()); let mut dirty_partitions: HashMap<MemcmpEncoded, OwnedRow> = HashMap::new();
447
448 for record in chunk.records() {
450 let input_row = must_match!(record, Record::Insert { new_row } => new_row);
451
452 let partition_key = input_row
453 .project(&this.partition_key_indices)
454 .into_owned_row();
455 let encoded_partition_key = memcmp_encoding::encode_row(
456 &partition_key,
457 &vec![OrderType::ascending(); this.partition_key_indices.len()],
458 )?;
459
460 Self::ensure_key_in_cache(
462 this,
463 &mut vars.partitions,
464 &partition_key,
465 &encoded_partition_key,
466 )
467 .await?;
468 let partition: &mut Partition =
469 &mut vars.partitions.get_mut(&encoded_partition_key).unwrap();
470
471 this.state_table.insert(input_row);
473
474 let order_key_enc = memcmp_encoding::encode_row(
476 row::once(Some(
477 input_row
478 .datum_at(this.order_key_index)
479 .expect("order key column must be non-NULL")
480 .into_scalar_impl(),
481 )),
482 &[OrderType::ascending()],
483 )?;
484 let pk = input_row.project(&this.input_stream_key).into_owned_row();
485 let key = StateKey {
486 order_key: order_key_enc,
487 pk: pk.into(),
488 };
489 for (call, state) in this.calls.iter().zip_eq_fast(partition.states.iter_mut()) {
490 state.append(
491 key.clone(),
492 input_row
493 .project(call.args.val_indices())
494 .into_owned_row()
495 .as_inner()
496 .into(),
497 );
498 }
499 partition
500 .curr_row_buffer
501 .push_back(input_row.into_owned_row());
502
503 let mut has_output = false;
504 while partition.states.are_ready() {
505 has_output = true;
506 let (ret_values, evict_hint) = partition.states.slide()?;
510 let curr_row = partition
511 .curr_row_buffer
512 .pop_front()
513 .expect("ready window must have corresponding current row");
514
515 for (builder, datum) in builders.iter_mut().zip_eq_debug(
517 curr_row
518 .iter()
519 .chain(ret_values.iter().map(|v| v.to_datum_ref())),
520 ) {
521 builder.append(datum);
522 }
523
524 if let StateEvictHint::CanEvict(keys_to_evict) = evict_hint {
526 for key in keys_to_evict {
527 let order_key = memcmp_encoding::decode_row(
528 &key.order_key,
529 &[this.schema[this.order_key_index].data_type()],
530 &[OrderType::ascending()],
531 )?;
532 let state_row = (&partition_key)
534 .chain(&order_key)
535 .chain(key.pk.as_inner())
536 .chain(row::once(Datum::None))
537 .project(&this.evict_row_indices);
538 this.state_table.delete(state_row);
541 }
542 }
543 }
544
545 if has_output && this.intermediate_state_table.is_some() {
546 dirty_partitions
547 .entry(encoded_partition_key)
548 .or_insert(partition_key);
549 }
550 }
551
552 for (encoded_partition_key, partition_key) in &dirty_partitions {
554 let partition = &mut *vars.partitions.get_mut(encoded_partition_key).unwrap();
555 Self::persist_intermediate_state(this, partition, partition_key);
556 }
557
558 let columns: Vec<ArrayRef> = builders.into_iter().map(|b| b.finish().into()).collect();
559 let chunk_size = columns[0].len();
560 Ok(if chunk_size > 0 {
561 Some(StreamChunk::new(vec![Op::Insert; chunk_size], columns))
562 } else {
563 None
564 })
565 }
566
567 #[try_stream(ok = Message, error = StreamExecutorError)]
568 async fn executor_inner(self) {
569 let EowcOverWindowExecutor {
570 input,
571 inner: mut this,
572 } = self;
573
574 let metrics_info = MetricsInfo::new(
575 this.actor_ctx.streaming_metrics.clone(),
576 this.state_table.table_id(),
577 this.actor_ctx.id,
578 "EowcOverWindow",
579 );
580
581 let mut vars = ExecutionVars {
582 partitions: ManagedLruCache::unbounded(this.watermark_sequence.clone(), metrics_info),
583 _phantom: PhantomData::<S>,
584 };
585
586 let mut input = input.execute();
587 let barrier = expect_first_barrier(&mut input).await?;
588 let first_epoch = barrier.epoch;
589 yield Message::Barrier(barrier);
590 this.state_table.init_epoch(first_epoch).await?;
591 if let Some(intermediate_state_table) = &mut this.intermediate_state_table {
592 intermediate_state_table.init_epoch(first_epoch).await?;
593 }
594
595 #[for_await]
596 for msg in input {
597 let msg = msg?;
598 match msg {
599 Message::Watermark(_) => {
600 continue;
601 }
602 Message::Chunk(chunk) => {
603 let output_chunk = Self::apply_chunk(&mut this, &mut vars, chunk).await?;
604 if let Some(chunk) = output_chunk {
605 yield Message::Chunk(chunk);
606 }
607 this.state_table.try_flush().await?;
608 if let Some(intermediate_state_table) = &mut this.intermediate_state_table {
609 intermediate_state_table.try_flush().await?;
610 }
611 }
612 Message::Barrier(barrier) => {
613 let post_commit = this.state_table.commit(barrier.epoch).await?;
614 let intermediate_post_commit = if let Some(intermediate_state_table) =
615 &mut this.intermediate_state_table
616 {
617 Some(intermediate_state_table.commit(barrier.epoch).await?)
618 } else {
619 None
620 };
621
622 vars.partitions.evict();
623
624 let update_vnode_bitmap = barrier.as_update_vnode_bitmap(this.actor_ctx.id);
625 yield Message::Barrier(barrier);
626
627 let mut cache_may_stale = false;
628 if let Some((_, stale)) = post_commit
629 .post_yield_barrier(update_vnode_bitmap.clone())
630 .await?
631 {
632 cache_may_stale = cache_may_stale || stale;
633 }
634 if let Some(intermediate_post_commit) = intermediate_post_commit
635 && let Some((_, stale)) = intermediate_post_commit
636 .post_yield_barrier(update_vnode_bitmap)
637 .await?
638 {
639 cache_may_stale = cache_may_stale || stale;
640 }
641 if cache_may_stale {
642 vars.partitions.clear();
643 }
644 }
645 }
646 }
647 }
648}