risingwave_stream/executor/mview/
cache.rs1use std::collections::HashSet;
16use std::ops::Deref as _;
17
18use bytes::Bytes;
19use futures::{StreamExt as _, stream};
20use itertools::Itertools as _;
21use risingwave_common::array::{Op, StreamChunk};
22use risingwave_common::catalog::{ConflictBehavior, checked_conflict_behaviors};
23use risingwave_common::row::{CompactedRow, OwnedRow, Row, RowExt as _};
24use risingwave_common::types::ScalarImpl;
25use risingwave_common::util::iter_util::ZipEqFast as _;
26use risingwave_common::util::sort_util::{OrderType, cmp_datum};
27use risingwave_common::util::value_encoding::{BasicSerde, ValueRowSerializer as _};
28use risingwave_storage::StateStore;
29use risingwave_storage::row_serde::value_serde::ValueRowSerde;
30
31use crate::cache::ManagedLruCache;
32use crate::common::metrics::MetricsInfo;
33use crate::common::table::state_table::StateTableInner;
34use crate::executor::StreamExecutorResult;
35use crate::executor::monitor::MaterializeCacheMetrics;
36use crate::task::AtomicU64Ref;
37
38pub struct MaterializeCache {
40 lru_cache: ManagedLruCache<Vec<u8>, CacheValue>,
41 row_serde: BasicSerde,
42 version_column_indices: Vec<u32>,
43 conflict_behavior: ConflictBehavior,
44 toastable_column_indices: Option<Vec<usize>>,
45 metrics: MaterializeCacheMetrics,
46}
47
48type CacheValue = Option<CompactedRow>;
49type ChangeBuffer = crate::common::change_buffer::ChangeBuffer<Vec<u8>, OwnedRow>;
50
51fn row_below_watermark<S: StateStore, SD: ValueRowSerde>(
60 row_serde: &BasicSerde,
61 table: &StateTableInner<S, SD>,
62 row: &CompactedRow,
63) -> StreamExecutorResult<bool> {
64 let Some(clean_watermark_index) = table.clean_watermark_index else {
65 return Ok(false);
66 };
67 let Some(watermark) = table
68 .get_pending_watermark()
69 .or(table.get_committed_watermark())
70 else {
71 return Ok(false);
72 };
73 let deserialized = row_serde.deserializer.deserialize(row.row.clone())?;
74 Ok(cmp_datum(
75 deserialized.datum_at(clean_watermark_index),
76 Some(watermark.as_scalar_ref_impl()),
77 OrderType::ascending(),
78 ) == std::cmp::Ordering::Less)
79}
80
81impl MaterializeCache {
82 pub fn new(
86 watermark_sequence: AtomicU64Ref,
87 metrics_info: MetricsInfo,
88 row_serde: BasicSerde,
89 version_column_indices: Vec<u32>,
90 conflict_behavior: ConflictBehavior,
91 toastable_column_indices: Option<Vec<usize>>,
92 materialize_cache_metrics: MaterializeCacheMetrics,
93 ) -> Option<Self> {
94 match conflict_behavior {
95 checked_conflict_behaviors!() => {}
96 ConflictBehavior::NoCheck => {
97 assert!(
98 toastable_column_indices.is_none(),
99 "when there are toastable columns, conflict handling must be enabled"
100 );
101 return None;
102 }
103 }
104
105 let lru_cache: ManagedLruCache<Vec<u8>, CacheValue> =
106 ManagedLruCache::unbounded(watermark_sequence, metrics_info);
107 Some(Self {
108 lru_cache,
109 row_serde,
110 version_column_indices,
111 conflict_behavior,
112 toastable_column_indices,
113 metrics: materialize_cache_metrics,
114 })
115 }
116
117 pub async fn handle_new<S: StateStore, SD: ValueRowSerde>(
120 &mut self,
121 chunk: StreamChunk,
122 table: &StateTableInner<S, SD>,
123 ) -> StreamExecutorResult<ChangeBuffer> {
124 if table.value_indices().is_some() {
125 unimplemented!("materialize cache cannot handle conflicts with partial table values");
128 };
129
130 let (data_chunk, ops) = chunk.clone().into_parts();
131 let values = data_chunk.serialize();
132
133 let key_chunk = data_chunk.project(table.pk_indices());
134
135 let pks = {
136 let mut pks = vec![vec![]; data_chunk.capacity()];
137 key_chunk
138 .rows_with_holes()
139 .zip_eq_fast(pks.iter_mut())
140 .for_each(|(r, vnode_and_pk)| {
141 if let Some(r) = r {
142 table.pk_serde().serialize(r, vnode_and_pk);
143 }
144 });
145 pks
146 };
147 let (_, vis) = key_chunk.into_parts();
148 let row_ops = ops
149 .iter()
150 .zip_eq_fast(pks.into_iter())
151 .zip_eq_fast(values.into_iter())
152 .zip_eq_fast(vis.iter())
153 .filter_map(|(((op, k), v), vis)| vis.then_some((*op, k, v)))
154 .collect_vec();
155
156 self.handle_inner(row_ops, table).await
157 }
158
159 async fn handle_inner<S: StateStore, SD: ValueRowSerde>(
160 &mut self,
161 row_ops: Vec<(Op, Vec<u8>, Bytes)>,
162 table: &StateTableInner<S, SD>,
163 ) -> StreamExecutorResult<ChangeBuffer> {
164 let key_set: HashSet<Box<[u8]>> = row_ops
165 .iter()
166 .map(|(_, k, _)| k.as_slice().into())
167 .collect();
168
169 self.fetch_keys(key_set.iter().map(|v| v.deref()), table)
172 .await?;
173
174 let mut change_buffer = ChangeBuffer::new();
175 let row_serde = self.row_serde.clone();
176 let version_column_indices = self.version_column_indices.clone();
177 for (op, key, row) in row_ops {
178 macro_rules! get_expected {
180 () => {
181 self.lru_cache.get(&key).unwrap_or_else(|| {
182 panic!(
183 "the key {:?} has not been fetched in the materialize executor's cache",
184 key
185 )
186 })
187 };
188 }
189
190 match op {
191 Op::Insert | Op::UpdateInsert => {
192 let Some(old_row) = get_expected!() else {
193 let new_row_deserialized =
195 row_serde.deserializer.deserialize(row.clone())?;
196 change_buffer.insert(key.clone(), new_row_deserialized);
197 self.lru_cache.put(key, Some(CompactedRow { row }));
198 continue;
199 };
200
201 match self.conflict_behavior {
203 ConflictBehavior::Overwrite => {
204 let old_row_deserialized =
205 row_serde.deserializer.deserialize(old_row.row.clone())?;
206 let new_row_deserialized =
207 row_serde.deserializer.deserialize(row.clone())?;
208
209 let need_overwrite = if !version_column_indices.is_empty() {
210 versions_are_newer_or_equal(
211 &old_row_deserialized,
212 &new_row_deserialized,
213 &version_column_indices,
214 )
215 } else {
216 true
218 };
219
220 if need_overwrite {
221 if let Some(toastable_indices) = &self.toastable_column_indices {
222 let final_row = toast::handle_toast_columns_for_postgres_cdc(
224 &old_row_deserialized,
225 &new_row_deserialized,
226 toastable_indices,
227 );
228
229 change_buffer.update(
230 key.clone(),
231 old_row_deserialized,
232 final_row.clone(),
233 );
234 let final_row_bytes =
235 Bytes::from(row_serde.serializer.serialize(final_row));
236 self.lru_cache.put(
237 key.clone(),
238 Some(CompactedRow {
239 row: final_row_bytes,
240 }),
241 );
242 } else {
243 change_buffer.update(
245 key.clone(),
246 old_row_deserialized,
247 new_row_deserialized,
248 );
249 self.lru_cache
250 .put(key.clone(), Some(CompactedRow { row: row.clone() }));
251 }
252 };
253 }
254 ConflictBehavior::IgnoreConflict => {
255 }
257 ConflictBehavior::DoUpdateIfNotNull => {
258 let old_row_deserialized =
261 row_serde.deserializer.deserialize(old_row.row.clone())?;
262 let new_row_deserialized =
263 row_serde.deserializer.deserialize(row.clone())?;
264 let need_overwrite = if !version_column_indices.is_empty() {
265 versions_are_newer_or_equal(
266 &old_row_deserialized,
267 &new_row_deserialized,
268 &version_column_indices,
269 )
270 } else {
271 true
272 };
273
274 if need_overwrite {
275 let mut row_deserialized_vec =
276 old_row_deserialized.clone().into_inner().into_vec();
277 replace_if_not_null(
278 &mut row_deserialized_vec,
279 new_row_deserialized.clone(),
280 );
281 let mut updated_row = OwnedRow::new(row_deserialized_vec);
282
283 if let Some(toastable_indices) = &self.toastable_column_indices {
285 let old_row_deserialized_again =
288 row_serde.deserializer.deserialize(old_row.row.clone())?;
289 updated_row = toast::handle_toast_columns_for_postgres_cdc(
290 &old_row_deserialized_again,
291 &updated_row,
292 toastable_indices,
293 );
294 }
295
296 change_buffer.update(
297 key.clone(),
298 old_row_deserialized,
299 updated_row.clone(),
300 );
301 let updated_row_bytes =
302 Bytes::from(row_serde.serializer.serialize(updated_row));
303 self.lru_cache.put(
304 key.clone(),
305 Some(CompactedRow {
306 row: updated_row_bytes,
307 }),
308 );
309 }
310 }
311 ConflictBehavior::NoCheck => unreachable!(),
312 };
313 }
314
315 Op::UpdateDelete
316 if matches!(
317 self.conflict_behavior,
318 ConflictBehavior::Overwrite | ConflictBehavior::DoUpdateIfNotNull
319 ) =>
320 {
321 }
331
332 Op::Delete | Op::UpdateDelete => {
333 if let Some(old_row) = get_expected!() {
334 let old_row_deserialized =
335 row_serde.deserializer.deserialize(old_row.row.clone())?;
336 change_buffer.delete(key.clone(), old_row_deserialized);
337 self.lru_cache.put(key, None);
339 } else {
340 }
343 }
344 }
345 }
346 Ok(change_buffer)
347 }
348
349 async fn fetch_keys<'a, S: StateStore, SD: ValueRowSerde>(
350 &mut self,
351 keys: impl Iterator<Item = &'a [u8]>,
352 table: &StateTableInner<S, SD>,
353 ) -> StreamExecutorResult<()> {
354 let mut futures = vec![];
355 for key in keys {
356 self.metrics.materialize_cache_total_count.inc();
357
358 if let Some(cached) = self.lru_cache.get(key) {
359 self.metrics.materialize_cache_hit_count.inc();
360 if let Some(row) = cached {
361 if row_below_watermark(&self.row_serde, table, row)? {
362 self.lru_cache.put(key.to_vec(), None);
363 } else {
364 self.metrics.materialize_data_exist_count.inc();
365 }
366 }
367 continue;
368 }
369
370 let row_serde = self.row_serde.clone();
371 futures.push(async move {
372 let key_row = table.pk_serde().deserialize(key).unwrap();
373 let row = table.get_row(key_row).await?.map(CompactedRow::from);
374 let row = match row {
375 Some(row) if row_below_watermark(&row_serde, table, &row)? => None,
376 other => other,
377 };
378 StreamExecutorResult::Ok((key.to_vec(), row))
379 });
380 }
381
382 let mut buffered = stream::iter(futures).buffer_unordered(10).fuse();
383 while let Some(result) = buffered.next().await {
384 let (key, row) = result?;
385 if row.is_some() {
386 self.metrics.materialize_data_exist_count.inc();
387 }
388 match self.conflict_behavior {
390 checked_conflict_behaviors!() => self.lru_cache.put(key, row),
391 _ => unreachable!(),
392 };
393 }
394
395 Ok(())
396 }
397
398 pub fn invalidate_rows<S: StateStore, SD: ValueRowSerde>(
401 &mut self,
402 rows: impl IntoIterator<Item = impl Row>,
403 table: &StateTableInner<S, SD>,
404 ) {
405 for row in rows {
406 let key = row
407 .project(table.pk_indices())
408 .memcmp_serialize(table.pk_serde());
409 self.lru_cache.remove(&key);
410 }
411 }
412
413 pub fn evict(&mut self) {
415 self.lru_cache.evict()
416 }
417
418 pub fn clear(&mut self) {
420 self.lru_cache.clear()
421 }
422}
423
424fn replace_if_not_null(row: &mut Vec<Option<ScalarImpl>>, replacement: OwnedRow) {
437 for (old_col, new_col) in row.iter_mut().zip_eq_fast(replacement) {
438 if let Some(new_value) = new_col {
439 *old_col = Some(new_value);
440 }
441 }
442}
443
444fn versions_are_newer_or_equal(
447 old_row: &OwnedRow,
448 new_row: &OwnedRow,
449 version_column_indices: &[u32],
450) -> bool {
451 if version_column_indices.is_empty() {
452 return true;
454 }
455
456 for &idx in version_column_indices {
457 let old_value = old_row.datum_at(idx as usize);
458 let new_value = new_row.datum_at(idx as usize);
459
460 match cmp_datum(old_value, new_value, OrderType::ascending_nulls_first()) {
461 std::cmp::Ordering::Less => return true, std::cmp::Ordering::Greater => return false, std::cmp::Ordering::Equal => continue, }
465 }
466
467 true
469}
470
471mod toast {
473 use risingwave_common::types::{DEBEZIUM_UNAVAILABLE_VALUE, DEBEZIUM_UNAVAILABLE_VECTOR_ELEM};
474
475 use super::*;
476
477 fn is_unavailable_value_str(s: &str) -> bool {
480 s.len() == DEBEZIUM_UNAVAILABLE_VALUE.len() && s == DEBEZIUM_UNAVAILABLE_VALUE
481 }
482
483 fn is_debezium_unavailable_value(
486 datum: &Option<risingwave_common::types::ScalarRefImpl<'_>>,
487 ) -> bool {
488 match datum {
489 Some(risingwave_common::types::ScalarRefImpl::Utf8(val)) => {
490 is_unavailable_value_str(val)
491 }
492 Some(risingwave_common::types::ScalarRefImpl::Jsonb(jsonb_ref)) => {
493 jsonb_ref
495 .as_str()
496 .map(is_unavailable_value_str)
497 .unwrap_or(false)
498 }
499 Some(risingwave_common::types::ScalarRefImpl::Bytea(bytea)) => {
500 if let Ok(bytea_str) = std::str::from_utf8(bytea) {
504 is_unavailable_value_str(bytea_str)
505 } else {
506 false
507 }
508 }
509 Some(risingwave_common::types::ScalarRefImpl::Vector(vec_ref)) => {
510 let slice = vec_ref.as_slice();
517 !slice.is_empty()
518 && slice
519 .iter()
520 .all(|f| f.0 == DEBEZIUM_UNAVAILABLE_VECTOR_ELEM)
521 }
522 Some(risingwave_common::types::ScalarRefImpl::List(list_ref))
526 if list_ref.len() == 1 =>
527 {
528 if let Some(Some(element)) = list_ref.get(0) {
529 is_debezium_unavailable_value(&Some(element))
531 } else {
532 false
533 }
534 }
535 _ => false,
536 }
537 }
538
539 pub fn handle_toast_columns_for_postgres_cdc(
541 old_row: &OwnedRow,
542 new_row: &OwnedRow,
543 toastable_indices: &[usize],
544 ) -> OwnedRow {
545 let mut fixed_row_data = new_row.as_inner().to_vec();
546
547 for &toast_idx in toastable_indices {
548 let is_unavailable = is_debezium_unavailable_value(&new_row.datum_at(toast_idx));
550 if is_unavailable {
551 if let Some(old_datum_ref) = old_row.datum_at(toast_idx) {
553 fixed_row_data[toast_idx] = Some(old_datum_ref.into_scalar_impl());
554 }
555 }
556 }
557
558 OwnedRow::new(fixed_row_data)
559 }
560}