1use std::collections::HashMap;
16use std::marker::PhantomData;
17use std::ops::Bound;
18
19use anyhow::Context;
20use futures::{TryStreamExt, stream};
21use itertools::Itertools;
22use risingwave_common::array::Op;
23use risingwave_common::array::stream_record::Record;
24use risingwave_common::row::RowExt;
25use risingwave_common::types::ToOwnedDatum;
26use risingwave_common::util::iter_util::ZipEqFast;
27use risingwave_common::util::memcmp_encoding::{self, MemcmpEncoded};
28use risingwave_common::util::row_serde::OrderedRowSerde;
29use risingwave_common::util::sort_util::OrderType;
30use risingwave_common::{must_match, row};
31use risingwave_common_estimate_size::EstimateSize;
32use risingwave_common_estimate_size::collections::EstimatedVecDeque;
33use risingwave_expr::window_function::{
34 Readiness, StateEvictHint, StateKey, WatermarkBound, WindowFuncCall, WindowStateSnapshot,
35 WindowStates, create_window_state,
36};
37use risingwave_pb::stream_plan::PbEowcOverWindowNodeVersion;
38use risingwave_pb::window_function::{
39 EowcPartitionProgress as PbEowcPartitionProgress, StateKey as PbStateKey,
40 WatermarkBound as PbWatermarkBound, WindowStateSnapshot as PbWindowStateSnapshot,
41};
42use risingwave_storage::store::PrefetchOptions;
43use tracing::debug;
44
45use super::watermark_trigger::{TriggerEntry, WatermarkTriggers};
46use crate::cache::ManagedLruCache;
47use crate::common::metrics::MetricsInfo;
48use crate::consistency::consistency_panic;
49use crate::executor::prelude::*;
50
51struct Partition {
52 states: WindowStates,
53 curr_row_buffer: EstimatedVecDeque<OwnedRow>,
54 intermediate_state_row: Option<OwnedRow>,
57 progress: PartitionProgress,
59 dirty: bool,
61}
62
63impl EstimateSize for Partition {
64 fn estimated_heap_size(&self) -> usize {
65 let mut total_size = self.curr_row_buffer.estimated_heap_size();
66 for state in self.states.iter() {
67 total_size += state.estimated_heap_size();
68 }
69 if let Some(row) = &self.intermediate_state_row {
70 total_size += row.estimated_heap_size();
71 }
72 total_size + self.progress.estimated_heap_size()
73 }
74}
75
76#[derive(Default, EstimateSize)]
79struct PartitionProgress {
80 last_output_key: Option<StateKey>,
84 trigger: Option<WatermarkBound>,
89}
90
91type PartitionCache = ManagedLruCache<MemcmpEncoded, Partition>; const LOAD_CONCURRENCY: usize = 10;
95
96fn encode_state_key(key: &StateKey, pk_ser: &OrderedRowSerde) -> PbStateKey {
97 PbStateKey {
98 order_key: key.order_key.to_vec(),
99 pk: key.pk.as_inner().memcmp_serialize(pk_ser),
100 }
101}
102
103fn decode_state_key(key: PbStateKey, pk_deser: &OrderedRowSerde) -> StreamExecutorResult<StateKey> {
104 let pk = pk_deser
105 .deserialize(&key.pk)
106 .context("failed to deserialize pk")?;
107 Ok(StateKey {
108 order_key: key.order_key.into(),
109 pk: pk.into(),
110 })
111}
112
113fn encode_snapshot(snapshot: &WindowStateSnapshot, pk_ser: &OrderedRowSerde) -> Vec<u8> {
115 use prost::Message;
116 let pb = PbWindowStateSnapshot {
117 last_output_key: snapshot
118 .last_output_key
119 .as_ref()
120 .map(|key| encode_state_key(key, pk_ser)),
121 function_state: Some(snapshot.function_state.clone()),
122 };
123 pb.encode_to_vec()
124}
125
126fn decode_snapshot(
128 bytes: &[u8],
129 pk_deser: &OrderedRowSerde,
130) -> StreamExecutorResult<WindowStateSnapshot> {
131 use prost::Message;
132 let pb = PbWindowStateSnapshot::decode(bytes).context("failed to decode snapshot")?;
133 let last_output_key = pb
134 .last_output_key
135 .map(|key| decode_state_key(key, pk_deser))
136 .transpose()?;
137 let function_state = pb
138 .function_state
139 .context("snapshot missing function_state")?;
140 Ok(WindowStateSnapshot {
141 last_output_key,
142 function_state,
143 })
144}
145
146fn encode_progress(progress: &PartitionProgress, pk_ser: &OrderedRowSerde) -> Option<Vec<u8>> {
148 use prost::Message;
149 if progress.last_output_key.is_none() && progress.trigger.is_none() {
150 return None;
151 }
152 let pb = PbEowcPartitionProgress {
153 last_output_key: progress
154 .last_output_key
155 .as_ref()
156 .map(|key| encode_state_key(key, pk_ser)),
157 trigger: progress.trigger.as_ref().map(|bound| PbWatermarkBound {
158 value: bound.value.to_vec(),
159 exclusive: bound.exclusive,
160 }),
161 };
162 Some(pb.encode_to_vec())
163}
164
165fn decode_progress(
166 bytes: &[u8],
167 pk_deser: &OrderedRowSerde,
168) -> StreamExecutorResult<PartitionProgress> {
169 use prost::Message;
170 let pb = PbEowcPartitionProgress::decode(bytes).context("failed to decode progress")?;
171 Ok(PartitionProgress {
172 last_output_key: pb
173 .last_output_key
174 .map(|key| decode_state_key(key, pk_deser))
175 .transpose()?,
176 trigger: pb.trigger.map(|bound| WatermarkBound {
177 value: bound.value.into(),
178 exclusive: bound.exclusive,
179 }),
180 })
181}
182
183pub struct EowcOverWindowExecutor<S: StateStore> {
220 input: Executor,
221 inner: ExecutorInner<S>,
222}
223
224struct ExecutorInner<S: StateStore> {
225 actor_ctx: ActorContextRef,
226
227 schema: Schema,
228 calls: Vec<WindowFuncCall>,
229 input_stream_key: Vec<usize>,
230 partition_key_indices: Vec<usize>,
231 order_key_index: usize, state_table: StateTable<S>,
233 evict_row_indices: Vec<usize>,
236 watermark_sequence: AtomicU64Ref,
237 intermediate_state_table: Option<StateTable<S>>,
240 pk_serde: Option<OrderedRowSerde>,
243 exact_readiness: bool,
246 triggers: Option<WatermarkTriggers<S>>,
249 chunk_size: usize,
250}
251
252impl<S: StateStore> ExecutorInner<S> {
253 fn encode_partition_key(&self, partition_key: impl Row) -> StreamExecutorResult<MemcmpEncoded> {
254 Ok(memcmp_encoding::encode_row(
255 partition_key,
256 &vec![OrderType::ascending(); self.partition_key_indices.len()],
257 )?)
258 }
259}
260
261struct ExecutionVars<S: StateStore> {
262 partitions: PartitionCache,
263 _phantom: PhantomData<S>,
264}
265
266impl<S: StateStore> Execute for EowcOverWindowExecutor<S> {
267 fn execute(self: Box<Self>) -> BoxedMessageStream {
268 self.executor_inner().boxed()
269 }
270}
271
272pub struct EowcOverWindowExecutorArgs<S: StateStore> {
273 pub actor_ctx: ActorContextRef,
274
275 pub input: Executor,
276
277 pub schema: Schema,
278 pub calls: Vec<WindowFuncCall>,
279 pub partition_key_indices: Vec<usize>,
280 pub order_key_index: usize,
281 pub state_table: StateTable<S>,
282 pub watermark_epoch: AtomicU64Ref,
283 pub intermediate_state_table: Option<StateTable<S>>,
286 pub watermark_trigger_table: Option<StateTable<S>>,
288 pub version: PbEowcOverWindowNodeVersion,
289 pub chunk_size: usize,
290}
291
292impl<S: StateStore> EowcOverWindowExecutor<S> {
293 pub fn new(args: EowcOverWindowExecutorArgs<S>) -> Self {
294 let input_info = args.input.info().clone();
295
296 debug_assert!(
298 args.state_table.pk_indices().iter().all(|i| {
299 args.partition_key_indices.contains(i)
300 || *i == args.order_key_index
301 || input_info.stream_key.contains(i)
302 }),
303 "state table pk must consist of partition key, order key and input stream key columns"
304 );
305 let num_partition_key_cols = args.partition_key_indices.len();
306 let evict_row_indices = (0..input_info.schema.len())
307 .map(|i| {
308 if let Some(j) = args.partition_key_indices.iter().position(|&k| k == i) {
309 j
310 } else if i == args.order_key_index {
311 num_partition_key_cols
312 } else if let Some(j) = input_info.stream_key.iter().position(|&k| k == i) {
313 num_partition_key_cols + 1 + j
314 } else {
315 num_partition_key_cols + 1 + input_info.stream_key.len()
317 }
318 })
319 .collect_vec();
320
321 let pk_serde = args.intermediate_state_table.as_ref().map(|_| {
323 let pk_data_types: Vec<_> = input_info
324 .stream_key
325 .iter()
326 .map(|&i| args.schema[i].data_type())
327 .collect();
328 let pk_order_types: Vec<_> = input_info
329 .stream_key
330 .iter()
331 .map(|_| OrderType::ascending())
332 .collect();
333 OrderedRowSerde::new(pk_data_types, pk_order_types)
334 });
335
336 let exact_readiness = args.version >= PbEowcOverWindowNodeVersion::Issue17119;
337 let triggers = args.watermark_trigger_table.map(|table| {
338 assert!(
339 exact_readiness && args.intermediate_state_table.is_some(),
340 "watermark trigger table is planned with the exact readiness and the progress column"
341 );
342 WatermarkTriggers::new(table, args.schema[args.order_key_index].data_type())
343 });
344
345 Self {
346 input: args.input,
347 inner: ExecutorInner {
348 actor_ctx: args.actor_ctx,
349 schema: args.schema,
350 calls: args.calls,
351 input_stream_key: input_info.stream_key,
352 partition_key_indices: args.partition_key_indices,
353 order_key_index: args.order_key_index,
354 state_table: args.state_table,
355 evict_row_indices,
356 watermark_sequence: args.watermark_epoch,
357 intermediate_state_table: args.intermediate_state_table,
358 pk_serde,
359 exact_readiness,
360 triggers,
361 chunk_size: args.chunk_size,
362 },
363 }
364 }
365
366 fn restore_intermediate_state(
369 this: &ExecutorInner<S>,
370 partition: &mut Partition,
371 partition_key: &OwnedRow,
372 row: Option<OwnedRow>,
373 ) -> StreamExecutorResult<()> {
374 if this.intermediate_state_table.is_none() {
375 return Ok(());
376 }
377 let pk_serde = this
378 .pk_serde
379 .as_ref()
380 .expect("pk_serde must be set when intermediate_state_table is present");
381
382 for state in partition.states.iter_mut() {
383 state.enable_persistence();
384 }
385
386 if let Some(row) = row {
387 let num_partition_key_cols = this.partition_key_indices.len();
388 let num_calls = this.calls.len();
389
390 for call_index in 0..num_calls {
391 let state_col = num_partition_key_cols + call_index;
392 if state_col < row.len() {
393 if let Some(state_bytes) = row.datum_at(state_col) {
394 let snapshot = decode_snapshot(state_bytes.into_bytea(), pk_serde)?;
395 debug!(
396 "Restoring intermediate state for partition {:?}, call_index {}, has_last_key: {}",
397 partition_key,
398 call_index,
399 snapshot.last_output_key.is_some()
400 );
401 partition
402 .states
403 .get_mut(call_index)
404 .unwrap()
405 .restore(snapshot)?;
406 }
407 } else {
408 return Err(anyhow::anyhow!(
409 "intermediate state row has fewer columns ({}) than expected ({}) \
410 at call_index {}, state may be corrupted",
411 row.len(),
412 num_partition_key_cols + num_calls,
413 call_index
414 )
415 .into());
416 }
417 }
418 if this.triggers.is_some()
419 && let Some(progress_bytes) = row.datum_at(num_partition_key_cols + num_calls)
420 {
421 partition.progress = decode_progress(progress_bytes.into_bytea(), pk_serde)?;
422 }
423 partition.intermediate_state_row = Some(row);
424 }
425 Ok(())
426 }
427
428 fn persist_partition(
430 this: &mut ExecutorInner<S>,
431 partition: &mut Partition,
432 partition_key: &OwnedRow,
433 ) {
434 if !std::mem::take(&mut partition.dirty) {
435 return;
436 }
437 let Some(intermediate_state_table) = &mut this.intermediate_state_table else {
438 return;
439 };
440 let pk_serde = this
441 .pk_serde
442 .as_ref()
443 .expect("pk_serde must be set when intermediate_state_table is present");
444
445 let num_calls = partition.states.len();
446 let num_partition_key_cols = partition_key.len();
447
448 let mut new_row_values = Vec::with_capacity(num_partition_key_cols + num_calls + 1);
450 for datum in partition_key.iter() {
451 new_row_values.push(datum.to_owned_datum());
452 }
453
454 for (call_index, state) in partition.states.iter().enumerate() {
456 if let Some(snapshot) = state.snapshot() {
457 let snapshot_bytes = encode_snapshot(&snapshot, pk_serde);
458 new_row_values.push(Some(snapshot_bytes.into_boxed_slice().into()));
459 } else if let Some(ref old_row) = partition.intermediate_state_row {
460 let state_col = num_partition_key_cols + call_index;
461 if state_col < old_row.len() {
462 new_row_values.push(old_row.datum_at(state_col).to_owned_datum());
463 } else {
464 new_row_values.push(None);
465 }
466 } else {
467 new_row_values.push(None);
468 }
469 }
470 if this.triggers.is_some() {
471 if partition.states.is_empty() {
472 partition.progress.last_output_key = None;
474 }
475 new_row_values.push(
476 encode_progress(&partition.progress, pk_serde)
477 .map(|bytes| bytes.into_boxed_slice().into()),
478 );
479 }
480 let new_row = OwnedRow::new(new_row_values);
481 let is_empty = new_row
482 .iter()
483 .skip(num_partition_key_cols)
484 .all(|datum| datum.is_none());
485
486 partition.intermediate_state_row = match partition.intermediate_state_row.take() {
487 Some(old_row) if is_empty => {
488 intermediate_state_table.delete(old_row);
489 None
490 }
491 Some(old_row) => {
492 intermediate_state_table.update(old_row, new_row.clone());
493 Some(new_row)
494 }
495 None if is_empty => None,
496 None => {
497 intermediate_state_table.insert(new_row.clone());
498 Some(new_row)
499 }
500 };
501 }
502
503 async fn load_partition(
506 this: &ExecutorInner<S>,
507 partition_key: &OwnedRow,
508 ) -> StreamExecutorResult<Partition> {
509 let mut partition = Partition {
510 states: WindowStates::new(this.calls.iter().map(create_window_state).try_collect()?),
511 curr_row_buffer: Default::default(),
512 intermediate_state_row: None,
513 progress: Default::default(),
514 dirty: false,
515 };
516
517 let sub_range: &(Bound<OwnedRow>, Bound<OwnedRow>) = &(Bound::Unbounded, Bound::Unbounded);
518 let (intermediate_state_row, table_iter) = futures::try_join!(
519 async {
520 match &this.intermediate_state_table {
521 Some(table) => table.get_row(partition_key).await,
522 None => Ok(None),
523 }
524 },
525 this.state_table
526 .iter_with_prefix(partition_key, sub_range, PrefetchOptions::default()),
527 )?;
528
529 Self::restore_intermediate_state(
531 this,
532 &mut partition,
533 partition_key,
534 intermediate_state_row,
535 )?;
536
537 #[for_await]
539 for keyed_row in table_iter {
540 let row = keyed_row?.into_owned_row();
541 let order_key_enc = memcmp_encoding::encode_row(
542 row::once(Some(
543 row.datum_at(this.order_key_index)
544 .expect("order key column must be non-NULL")
545 .into_scalar_impl(),
546 )),
547 &[OrderType::ascending()],
548 )?;
549 let pk = (&row).project(&this.input_stream_key).into_owned_row();
550 let key = StateKey {
551 order_key: order_key_enc,
552 pk: pk.into(),
553 };
554 for (call, state) in this.calls.iter().zip_eq_fast(partition.states.iter_mut()) {
555 state.append(
556 key.clone(),
557 (&row)
558 .project(call.args.val_indices())
559 .into_owned_row()
560 .as_inner()
561 .into(),
562 );
563 }
564 partition.curr_row_buffer.push_back(row);
565 }
566
567 assert!(partition.states.are_aligned());
569
570 Ok(partition)
571 }
572
573 fn skip_output_rows(
576 this: &mut ExecutorInner<S>,
577 partition: &mut Partition,
578 partition_key: &OwnedRow,
579 ) -> StreamExecutorResult<()> {
580 if !this.exact_readiness {
581 while partition.states.are_ready() {
584 partition.states.just_slide()?;
585 partition.curr_row_buffer.pop_front();
586 }
587 return Ok(());
588 }
589
590 while let Some(key) = partition.states.curr_key() {
591 let output = if this.triggers.is_some() {
592 partition
594 .progress
595 .last_output_key
596 .as_ref()
597 .is_some_and(|last_output_key| key <= last_output_key)
598 } else {
599 partition.states.readiness().is_ready(None)
602 };
603 if !output {
604 break;
605 }
606 let evict_hint = partition.states.slide_no_output()?;
607 partition.curr_row_buffer.pop_front();
608 Self::evict(this, partition_key, evict_hint)?;
609 }
610 Ok(())
611 }
612
613 async fn ensure_partition_in_cache(
614 this: &mut ExecutorInner<S>,
615 cache: &mut PartitionCache,
616 encoded_partition_key: &MemcmpEncoded,
617 partition_key: &OwnedRow,
618 ) -> StreamExecutorResult<()> {
619 if cache.contains(encoded_partition_key) {
620 return Ok(());
621 }
622 Self::ensure_partitions_in_cache(
623 this,
624 cache,
625 &[(encoded_partition_key.clone(), partition_key)],
626 )
627 .await
628 }
629
630 async fn ensure_partitions_in_cache(
632 this: &mut ExecutorInner<S>,
633 cache: &mut PartitionCache,
634 partitions: &[(MemcmpEncoded, &OwnedRow)],
635 ) -> StreamExecutorResult<()> {
636 let loaded: Vec<_> = {
637 let this = &*this;
638 stream::iter(
639 partitions
640 .iter()
641 .filter(|(encoded_partition_key, _)| !cache.contains(encoded_partition_key))
642 .unique_by(|(encoded_partition_key, _)| encoded_partition_key.clone())
643 .map(|(encoded_partition_key, partition_key)| async move {
644 let partition = Self::load_partition(this, partition_key).await?;
645 Ok::<_, StreamExecutorError>((
646 encoded_partition_key,
647 partition_key,
648 partition,
649 ))
650 }),
651 )
652 .buffer_unordered(LOAD_CONCURRENCY)
653 .try_collect()
654 .await?
655 };
656 for (encoded_partition_key, partition_key, mut partition) in loaded {
657 Self::skip_output_rows(this, &mut partition, partition_key)?;
658 cache.put(encoded_partition_key.clone(), partition);
659 }
660 Ok(())
661 }
662
663 fn reset_partition(
666 this: &ExecutorInner<S>,
667 partition: &mut Partition,
668 ) -> StreamExecutorResult<()> {
669 let mut states =
670 WindowStates::new(this.calls.iter().map(create_window_state).try_collect()?);
671 for (state, old_state) in states.iter_mut().zip_eq_fast(partition.states.iter()) {
672 if this.intermediate_state_table.is_some() {
673 state.enable_persistence();
674 }
675 if let Some(snapshot) = old_state.snapshot() {
676 state.restore(snapshot)?;
677 }
678 }
679 partition.states = states;
680 partition.curr_row_buffer = Default::default();
681 Ok(())
682 }
683
684 fn evict(
686 this: &mut ExecutorInner<S>,
687 partition_key: &OwnedRow,
688 evict_hint: StateEvictHint,
689 ) -> StreamExecutorResult<()> {
690 let StateEvictHint::CanEvict(keys_to_evict) = evict_hint else {
691 return Ok(());
692 };
693 for key in keys_to_evict {
694 let order_key = memcmp_encoding::decode_row(
695 &key.order_key,
696 &[this.schema[this.order_key_index].data_type()],
697 &[OrderType::ascending()],
698 )?;
699 let state_row = partition_key
701 .chain(&order_key)
702 .chain(key.pk.as_inner())
703 .chain(row::once(Datum::None))
704 .project(&this.evict_row_indices);
705 this.state_table.delete(state_row);
708 }
709 Ok(())
710 }
711
712 fn output_ready_rows(
715 this: &mut ExecutorInner<S>,
716 partition: &mut Partition,
717 partition_key: &OwnedRow,
718 chunk_builder: &mut StreamChunkBuilder,
719 chunks: &mut Vec<StreamChunk>,
720 ) -> StreamExecutorResult<Readiness> {
721 loop {
722 let readiness = if this.exact_readiness {
723 partition.states.readiness()
724 } else if partition.states.are_ready() {
725 Readiness::Ready
726 } else {
727 Readiness::WaitForInput
728 };
729 let watermark = this
730 .triggers
731 .as_ref()
732 .and_then(|triggers| triggers.watermark());
733 if !readiness.is_ready(watermark) {
734 return Ok(readiness);
735 }
736 if this.triggers.is_some() {
737 partition.progress.last_output_key = partition.states.curr_key().cloned();
738 }
739 partition.dirty = true;
740
741 let (ret_values, evict_hint) = partition.states.slide()?;
743 let curr_row = partition
744 .curr_row_buffer
745 .pop_front()
746 .expect("ready window must have corresponding current row");
747 if let Some(chunk) =
748 chunk_builder.append_row(Op::Insert, (&curr_row).chain(OwnedRow::new(ret_values)))
749 {
750 chunks.push(chunk);
751 }
752
753 Self::evict(this, partition_key, evict_hint)?;
754 }
755 }
756
757 fn register_trigger(
761 this: &mut ExecutorInner<S>,
762 partition: &mut Partition,
763 partition_key: &OwnedRow,
764 readiness: Readiness,
765 ) {
766 let Some(triggers) = &mut this.triggers else {
767 return;
768 };
769 if let Some(trigger) = partition.progress.trigger.clone() {
770 if triggers
771 .watermark()
772 .is_some_and(|watermark| trigger.is_satisfied_by(watermark))
773 {
774 consistency_panic!(
777 ?partition_key,
778 ?trigger,
779 "watermark trigger of the partition is lost"
780 );
781 } else if let Readiness::OnWatermark(bound) = &readiness
782 && bound < &trigger
783 {
784 triggers.remove(&TriggerEntry {
787 bound: trigger,
788 partition_key: partition_key.clone(),
789 });
790 } else {
791 return;
792 }
793 partition.progress.trigger = None;
794 partition.dirty = true;
795 }
796 let Readiness::OnWatermark(bound) = readiness else {
797 return;
798 };
799 assert!(
802 !triggers
803 .watermark()
804 .is_some_and(|watermark| bound.is_satisfied_by(watermark)),
805 "rows closed by the current watermark must have been output"
806 );
807 triggers.register(TriggerEntry {
808 bound: bound.clone(),
809 partition_key: partition_key.clone(),
810 });
811 partition.progress.trigger = Some(bound);
812 partition.dirty = true;
813 }
814
815 async fn apply_chunk(
816 this: &mut ExecutorInner<S>,
817 vars: &mut ExecutionVars<S>,
818 chunk: StreamChunk,
819 ) -> StreamExecutorResult<Vec<StreamChunk>> {
820 let mut chunk_builder = StreamChunkBuilder::new(this.chunk_size, this.schema.data_types());
821 let mut chunks = vec![];
822 let mut dirty_partitions: HashMap<MemcmpEncoded, OwnedRow> = HashMap::new();
824
825 for record in chunk.records() {
827 let input_row = must_match!(record, Record::Insert { new_row } => new_row);
828
829 let partition_key = input_row
830 .project(&this.partition_key_indices)
831 .into_owned_row();
832 let encoded_partition_key = this.encode_partition_key(&partition_key)?;
833
834 Self::ensure_partition_in_cache(
836 this,
837 &mut vars.partitions,
838 &encoded_partition_key,
839 &partition_key,
840 )
841 .await?;
842 let partition: &mut Partition =
843 &mut vars.partitions.get_mut(&encoded_partition_key).unwrap();
844
845 this.state_table.insert(input_row);
847
848 let order_key_enc = memcmp_encoding::encode_row(
850 row::once(Some(
851 input_row
852 .datum_at(this.order_key_index)
853 .expect("order key column must be non-NULL")
854 .into_scalar_impl(),
855 )),
856 &[OrderType::ascending()],
857 )?;
858 let pk = input_row.project(&this.input_stream_key).into_owned_row();
859 let key = StateKey {
860 order_key: order_key_enc,
861 pk: pk.into(),
862 };
863 for (call, state) in this.calls.iter().zip_eq_fast(partition.states.iter_mut()) {
864 state.append(
865 key.clone(),
866 input_row
867 .project(call.args.val_indices())
868 .into_owned_row()
869 .as_inner()
870 .into(),
871 );
872 }
873 partition
874 .curr_row_buffer
875 .push_back(input_row.into_owned_row());
876
877 let readiness = Self::output_ready_rows(
878 this,
879 partition,
880 &partition_key,
881 &mut chunk_builder,
882 &mut chunks,
883 )?;
884 Self::register_trigger(this, partition, &partition_key, readiness);
885
886 if this.intermediate_state_table.is_some() && partition.dirty {
887 dirty_partitions
888 .entry(encoded_partition_key)
889 .or_insert(partition_key);
890 }
891 }
892
893 for (encoded_partition_key, partition_key) in &dirty_partitions {
894 let partition = &mut *vars.partitions.get_mut(encoded_partition_key).unwrap();
895 Self::persist_partition(this, partition, partition_key);
896 }
897
898 chunks.extend(chunk_builder.take());
899 Ok(chunks)
900 }
901
902 #[try_stream(ok = StreamChunk, error = StreamExecutorError)]
904 async fn apply_watermark<'a>(
905 this: &'a mut ExecutorInner<S>,
906 vars: &'a mut ExecutionVars<S>,
907 watermark: ScalarImpl,
908 ) {
909 let Some(triggers) = &mut this.triggers else {
910 return Ok(());
911 };
912 if !triggers.advance_watermark(watermark)? {
913 return Ok(());
914 }
915
916 let mut chunk_builder = StreamChunkBuilder::new(this.chunk_size, this.schema.data_types());
917 loop {
920 let entries = (this.triggers.as_mut().unwrap())
921 .pop_due(this.chunk_size)
922 .await?;
923 if entries.is_empty() {
924 break;
925 }
926 let partitions: Vec<_> = entries
927 .iter()
928 .map(|entry| {
929 Ok::<_, StreamExecutorError>((
930 this.encode_partition_key(&entry.partition_key)?,
931 &entry.partition_key,
932 ))
933 })
934 .try_collect()?;
935 Self::ensure_partitions_in_cache(this, &mut vars.partitions, &partitions).await?;
936
937 for (entry, (encoded_partition_key, _)) in entries.iter().zip_eq_fast(&partitions) {
938 let mut chunks = vec![];
939 {
940 let partition = &mut *vars
941 .partitions
942 .get_mut(encoded_partition_key)
943 .expect("partitions of the chunk must be in the cache");
944 Self::fire_trigger(this, partition, entry, &mut chunk_builder, &mut chunks)?;
945 }
946 for chunk in chunks {
947 yield chunk;
948 }
949 }
950 Self::try_flush(this).await?;
951 vars.partitions.evict();
952 }
953
954 if let Some(chunk) = chunk_builder.take() {
955 yield chunk;
956 }
957 (this.triggers.as_mut().unwrap()).clean();
958 }
959
960 fn fire_trigger(
963 this: &mut ExecutorInner<S>,
964 partition: &mut Partition,
965 entry: &TriggerEntry,
966 chunk_builder: &mut StreamChunkBuilder,
967 chunks: &mut Vec<StreamChunk>,
968 ) -> StreamExecutorResult<()> {
969 let trigger = partition.progress.trigger.take();
970 if trigger.as_ref() != Some(&entry.bound) {
971 consistency_panic!(
972 partition_key = ?entry.partition_key,
973 bound = ?entry.bound,
974 progress_trigger = ?trigger,
975 "watermark trigger table diverges from the partition progress"
976 );
977 if let Some(trigger) = trigger
979 && trigger > entry.bound
980 {
981 partition.progress.trigger = Some(trigger);
982 }
983 }
984 partition.dirty = true;
985
986 let readiness =
987 Self::output_ready_rows(this, partition, &entry.partition_key, chunk_builder, chunks)?;
988 Self::register_trigger(this, partition, &entry.partition_key, readiness);
989 Self::persist_partition(this, partition, &entry.partition_key);
990 if partition.states.is_empty() {
991 Self::reset_partition(this, partition)?;
992 }
993 Ok(())
994 }
995
996 async fn try_flush(this: &mut ExecutorInner<S>) -> StreamExecutorResult<()> {
997 this.state_table.try_flush().await?;
998 if let Some(intermediate_state_table) = &mut this.intermediate_state_table {
999 intermediate_state_table.try_flush().await?;
1000 }
1001 if let Some(triggers) = &mut this.triggers {
1002 triggers.try_flush().await?;
1003 }
1004 Ok(())
1005 }
1006
1007 #[try_stream(ok = Message, error = StreamExecutorError)]
1008 async fn executor_inner(self) {
1009 let EowcOverWindowExecutor {
1010 input,
1011 inner: mut this,
1012 } = self;
1013
1014 let metrics_info = MetricsInfo::new(
1015 this.actor_ctx.streaming_metrics.clone(),
1016 this.state_table.table_id(),
1017 this.actor_ctx.id,
1018 "EowcOverWindow",
1019 );
1020
1021 let mut vars = ExecutionVars {
1022 partitions: ManagedLruCache::unbounded(this.watermark_sequence.clone(), metrics_info),
1023 _phantom: PhantomData::<S>,
1024 };
1025
1026 let mut input = input.execute();
1027 let barrier = expect_first_barrier(&mut input).await?;
1028 let first_epoch = barrier.epoch;
1029 yield Message::Barrier(barrier);
1030 this.state_table.init_epoch(first_epoch).await?;
1031 if let Some(intermediate_state_table) = &mut this.intermediate_state_table {
1032 intermediate_state_table.init_epoch(first_epoch).await?;
1033 }
1034 if let Some(triggers) = &mut this.triggers {
1035 triggers.init_epoch(first_epoch).await?;
1036 }
1037
1038 #[for_await]
1039 for msg in input {
1040 let msg = msg?;
1041 match msg {
1042 Message::Watermark(watermark) => {
1044 if watermark.col_idx == this.order_key_index {
1045 #[for_await]
1046 for chunk in Self::apply_watermark(&mut this, &mut vars, watermark.val) {
1047 yield Message::Chunk(chunk?);
1048 }
1049 }
1050 }
1051 Message::Chunk(chunk) => {
1052 for chunk in Self::apply_chunk(&mut this, &mut vars, chunk).await? {
1053 yield Message::Chunk(chunk);
1054 }
1055 Self::try_flush(&mut this).await?;
1056 }
1057 Message::Barrier(barrier) => {
1058 let post_commit = this.state_table.commit(barrier.epoch).await?;
1059 let intermediate_post_commit = if let Some(intermediate_state_table) =
1060 &mut this.intermediate_state_table
1061 {
1062 Some(intermediate_state_table.commit(barrier.epoch).await?)
1063 } else {
1064 None
1065 };
1066 let triggers_post_commit = if let Some(triggers) = &mut this.triggers {
1067 Some(triggers.commit(barrier.epoch).await?)
1068 } else {
1069 None
1070 };
1071
1072 vars.partitions.evict();
1073
1074 let update_vnode_bitmap = barrier.as_update_vnode_bitmap(this.actor_ctx.id);
1075 yield Message::Barrier(barrier);
1076
1077 let mut cache_may_stale = false;
1078 if let Some((_, stale)) = post_commit
1079 .post_yield_barrier(update_vnode_bitmap.clone())
1080 .await?
1081 {
1082 cache_may_stale = cache_may_stale || stale;
1083 }
1084 if let Some(intermediate_post_commit) = intermediate_post_commit
1085 && let Some((_, stale)) = intermediate_post_commit
1086 .post_yield_barrier(update_vnode_bitmap.clone())
1087 .await?
1088 {
1089 cache_may_stale = cache_may_stale || stale;
1090 }
1091 if let Some(triggers_post_commit) = triggers_post_commit {
1092 triggers_post_commit
1093 .post_yield_barrier(update_vnode_bitmap)
1094 .await?;
1095 }
1096 if cache_may_stale {
1097 vars.partitions.clear();
1098 }
1099 }
1100 }
1101 }
1102 }
1103}