1use std::mem;
16use std::sync::Arc;
17use std::sync::atomic::{AtomicU64, Ordering};
18
19use api::v1::{BulkWalEntry, Mutation, OpType, Rows, WalEntry, WriteHint};
20use futures::stream::{FuturesUnordered, StreamExt};
21use snafu::ResultExt;
22use store_api::logstore::LogStore;
23use store_api::logstore::provider::Provider;
24use store_api::storage::{RegionId, SequenceNumber};
25
26use crate::error::{Error, Result, UnexpectedSnafu, WriteGroupSnafu};
27use crate::memtable::KeyValues;
28use crate::memtable::bulk::part::BulkPart;
29use crate::metrics;
30use crate::region::version::{VersionControlData, VersionControlRef, VersionRef};
31use crate::request::OptionOutputTx;
32use crate::wal::{EntryId, WalWriter};
33
34struct WriteNotify {
36 err: Option<Arc<Error>>,
38 sender: OptionOutputTx,
40 num_rows: usize,
42}
43
44impl WriteNotify {
45 fn new(sender: OptionOutputTx, num_rows: usize) -> WriteNotify {
47 WriteNotify {
48 err: None,
49 sender,
50 num_rows,
51 }
52 }
53
54 fn notify_result(&mut self) {
56 if let Some(err) = &self.err {
57 self.sender
59 .send_mut(Err(err.clone()).context(WriteGroupSnafu));
60 } else {
61 self.sender.send_mut(Ok(self.num_rows));
63 }
64 }
65}
66
67impl Drop for WriteNotify {
68 fn drop(&mut self) {
69 if self.err.is_none() && std::thread::panicking() {
71 self.err = Some(Arc::new(
72 UnexpectedSnafu {
73 reason: "the write was interrupted by a panic",
74 }
75 .build(),
76 ));
77 }
78 self.notify_result();
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub(crate) enum WriteSource {
85 Request,
86 WalReplay,
87}
88
89pub(crate) struct RegionWriteCtx {
91 source: WriteSource,
93 has_skip_wal_writes: bool,
95 region_id: RegionId,
97 version: VersionRef,
99 version_control: VersionControlRef,
101 next_sequence: SequenceNumber,
105 next_entry_id: EntryId,
107 wal_entry: WalEntry,
112 memtable_mutations: Vec<(Mutation, WriteNotify)>,
114 provider: Provider,
116 wal_notifiers: Vec<WriteNotify>,
120 bulk_notifiers: Vec<WriteNotify>,
122 pub(crate) bulk_parts: Vec<BulkPart>,
124 failed: bool,
126
127 pub(crate) put_num: usize,
130 pub(crate) delete_num: usize,
132 pub(crate) written_bytes: Option<Arc<AtomicU64>>,
134}
135
136impl RegionWriteCtx {
137 pub(crate) fn new(
139 region_id: RegionId,
140 version_control: &VersionControlRef,
141 provider: Provider,
142 written_bytes: Option<Arc<AtomicU64>>,
143 source: WriteSource,
144 ) -> RegionWriteCtx {
145 let VersionControlData {
146 version,
147 committed_sequence,
148 last_entry_id,
149 ..
150 } = version_control.current();
151
152 RegionWriteCtx {
153 source,
154 has_skip_wal_writes: false,
155 region_id,
156 version,
157 version_control: version_control.clone(),
158 next_sequence: committed_sequence + 1,
159 next_entry_id: last_entry_id + 1,
160 wal_entry: WalEntry::default(),
161 memtable_mutations: Vec::new(),
162 provider,
163 wal_notifiers: Vec::new(),
164 bulk_notifiers: vec![],
165 failed: false,
166 put_num: 0,
167 delete_num: 0,
168 bulk_parts: vec![],
169 written_bytes,
170 }
171 }
172
173 pub(crate) fn push_mutation(
176 &mut self,
177 op_type: i32,
178 rows: Option<Rows>,
179 write_hint: Option<WriteHint>,
180 tx: OptionOutputTx,
181 sequence: Option<SequenceNumber>,
182 skip_wal: bool,
183 ) {
184 if let Some(sequence) = sequence {
185 self.next_sequence = sequence;
186 }
187 let num_rows = rows.as_ref().map(|rows| rows.rows.len()).unwrap_or(0);
188 self.has_skip_wal_writes |= num_rows > 0 && self.is_skip_wal_write(skip_wal);
189 let mutation = Mutation {
190 op_type,
191 sequence: self.next_sequence,
192 rows,
193 write_hint,
194 };
195
196 let notify = WriteNotify::new(tx, num_rows);
199 if skip_wal {
200 self.memtable_mutations.push((mutation, notify));
201 } else {
202 self.wal_entry.mutations.push(mutation);
203 self.wal_notifiers.push(notify);
204 }
205
206 self.next_sequence += num_rows as u64;
208
209 match OpType::try_from(op_type) {
211 Ok(OpType::Delete) => self.delete_num += num_rows,
212 Ok(OpType::Put) => self.put_num += num_rows,
213 Err(_) => (),
214 }
215 }
216
217 pub(crate) fn add_wal_entry<S: LogStore>(
219 &mut self,
220 wal_writer: &mut WalWriter<S>,
221 ) -> Result<()> {
222 wal_writer.add_entry(
223 self.region_id,
224 self.next_entry_id,
225 &self.wal_entry,
226 &self.provider,
227 )?;
228 self.next_entry_id += 1;
229 Ok(())
230 }
231
232 pub(crate) fn version(&self) -> &VersionRef {
233 &self.version
234 }
235
236 #[cfg(test)]
237 pub(crate) fn version_control(&self) -> &VersionControlRef {
238 &self.version_control
239 }
240
241 pub(crate) fn skip_wal(&self) -> bool {
243 self.provider == Provider::Noop
244 || self.version.options.skip_wal
245 || (self.wal_entry.mutations.is_empty() && self.wal_entry.bulk_entries.is_empty())
246 }
247
248 pub(crate) fn set_error(&mut self, err: Arc<Error>) {
250 for notify in self
252 .wal_notifiers
253 .iter_mut()
254 .chain(self.memtable_mutations.iter_mut().map(|(_, notify)| notify))
255 {
256 notify.err = Some(err.clone());
257 }
258 for notify in &mut self.bulk_notifiers {
259 notify.err = Some(err.clone());
260 }
261
262 self.failed = true;
264 }
265
266 pub(crate) fn is_failed(&self) -> bool {
268 self.failed
269 }
270
271 pub(crate) fn set_next_entry_id(&mut self, next_entry_id: EntryId) {
273 self.next_entry_id = next_entry_id
274 }
275
276 #[cfg(test)]
278 pub(crate) fn next_entry_id(&self) -> EntryId {
279 self.next_entry_id
280 }
281
282 pub(crate) async fn write_memtables(&mut self) {
284 if self.failed {
285 return;
286 }
287 if self.has_skip_wal_writes {
289 self.version.memtables.mutable.mark_skip_wal_writes();
290 }
291 self.write_mutations().await;
292 self.write_bulk().await;
293 self.publish_sequence_and_entry_id();
294 }
295
296 fn is_skip_wal_write(&self, request_skip_wal: bool) -> bool {
297 self.source == WriteSource::Request
298 && (request_skip_wal
299 || self.provider == Provider::Noop
300 || self.version.options.skip_wal)
301 }
302
303 async fn write_mutations(&mut self) {
305 debug_assert_eq!(self.wal_notifiers.len(), self.wal_entry.mutations.len());
306
307 if self.failed {
308 return;
309 }
310
311 let mutable_memtable = self.version.memtables.mutable.clone();
312 let prev_memory_usage = if self.written_bytes.is_some() {
313 Some(mutable_memtable.memory_usage())
314 } else {
315 None
316 };
317
318 let mut mutations = mem::take(&mut self.wal_entry.mutations)
319 .into_iter()
320 .zip(&mut self.wal_notifiers)
321 .chain(
322 self.memtable_mutations
323 .iter_mut()
324 .map(|(mutation, notify)| (mem::take(mutation), notify)),
326 )
327 .filter_map(|(mutation, notify)| {
328 let kvs = KeyValues::new(&self.version.metadata, mutation)?;
329 Some((notify, kvs))
330 })
331 .collect::<Vec<_>>();
332
333 if mutations.len() == 1 {
334 if let Err(err) = mutable_memtable.write(&mutations[0].1) {
335 mutations[0].0.err = Some(Arc::new(err));
336 }
337 } else {
338 let mut tasks = FuturesUnordered::new();
339 for (notify, kvs) in mutations {
340 let mutable = mutable_memtable.clone();
341 let task = common_runtime::spawn_blocking_global(move || mutable.write(&kvs));
343 tasks.push(async move { (notify, task.await) });
344 }
345
346 while let Some((notify, result)) = tasks.next().await {
347 if let Err(err) = result.unwrap() {
349 notify.err = Some(Arc::new(err));
350 }
351 }
352 }
353
354 if let Some(written_bytes) = &self.written_bytes {
355 let new_memory_usage = mutable_memtable.memory_usage();
356 let bytes = new_memory_usage.saturating_sub(prev_memory_usage.unwrap_or_default());
357 written_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
358 }
359 }
360
361 pub(crate) fn push_bulk(
362 &mut self,
363 sender: OptionOutputTx,
364 mut bulk: BulkPart,
365 sequence: Option<SequenceNumber>,
366 skip_wal: bool,
367 ) -> bool {
368 if let Some(sequence) = sequence {
369 self.next_sequence = sequence;
370 }
371 bulk.sequence = self.next_sequence;
372 bulk.min_sequence = self.next_sequence;
373 if !skip_wal {
374 let entry = match BulkWalEntry::try_from(&bulk) {
375 Ok(entry) => entry,
376 Err(e) => {
377 sender.send(Err(e));
378 return false;
379 }
380 };
381 self.wal_entry.bulk_entries.push(entry);
382 }
383
384 self.has_skip_wal_writes |= bulk.num_rows() > 0 && self.is_skip_wal_write(skip_wal);
385 self.bulk_notifiers
386 .push(WriteNotify::new(sender, bulk.num_rows()));
387
388 self.next_sequence += bulk.num_rows() as u64;
389 self.bulk_parts.push(bulk);
390 true
391 }
392
393 async fn write_bulk(&mut self) {
394 if self.failed || self.bulk_parts.is_empty() {
395 return;
396 }
397 #[cfg(test)]
398 test_hooks::pause_before_bulk_install(self.region_id, &self.version_control).await;
399 let _timer = metrics::REGION_WORKER_HANDLE_WRITE_ELAPSED
400 .with_label_values(&["write_bulk"])
401 .start_timer();
402
403 let mutable_memtable = &self.version.memtables.mutable;
404 let prev_memory_usage = if self.written_bytes.is_some() {
405 Some(mutable_memtable.memory_usage())
406 } else {
407 None
408 };
409
410 if self.bulk_parts.len() == 1 {
411 let part = self.bulk_parts.swap_remove(0);
412 let num_rows = part.num_rows();
413 if let Err(e) = self.version.memtables.mutable.write_bulk(part) {
414 self.bulk_notifiers[0].err = Some(Arc::new(e));
415 } else {
416 self.put_num += num_rows;
417 }
418 return;
419 }
420
421 let mut tasks = FuturesUnordered::new();
422 for (i, part) in self.bulk_parts.drain(..).enumerate() {
423 let mutable = mutable_memtable.clone();
424 tasks.push(common_runtime::spawn_blocking_global(move || {
425 let num_rows = part.num_rows();
426 (i, mutable.write_bulk(part), num_rows)
427 }));
428 }
429 while let Some(result) = tasks.next().await {
430 let (i, result, num_rows) = result.unwrap();
432 if let Err(err) = result {
433 self.bulk_notifiers[i].err = Some(Arc::new(err));
434 } else {
435 self.put_num += num_rows;
436 }
437 }
438
439 if let Some(written_bytes) = &self.written_bytes {
440 let new_memory_usage = mutable_memtable.memory_usage();
441 let bytes = new_memory_usage.saturating_sub(prev_memory_usage.unwrap_or_default());
442 written_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
443 }
444 }
445
446 fn publish_sequence_and_entry_id(&self) {
451 if self.failed {
452 return;
453 }
454 self.version_control
455 .set_sequence_and_entry_id(self.next_sequence - 1, self.next_entry_id - 1);
456 }
457}
458
459#[cfg(test)]
461pub(crate) mod test_hooks {
462 use std::sync::Mutex;
463 use std::sync::atomic::{AtomicU64, Ordering};
464
465 use store_api::storage::RegionId;
466 use tokio::sync::watch;
467
468 use crate::region::version::VersionControlRef;
469
470 struct ActiveBarrier {
473 id: u64,
474 target_region_id: RegionId,
477 target_version_control: VersionControlRef,
478 reached: watch::Sender<bool>,
479 release: watch::Sender<bool>,
480 }
481
482 static ACTIVE_BARRIERS: Mutex<Vec<ActiveBarrier>> = Mutex::new(Vec::new());
483 static NEXT_BARRIER_ID: AtomicU64 = AtomicU64::new(1);
484
485 fn lock_active_barriers() -> std::sync::MutexGuard<'static, Vec<ActiveBarrier>> {
486 ACTIVE_BARRIERS
489 .lock()
490 .unwrap_or_else(|poisoned| poisoned.into_inner())
491 }
492
493 pub(crate) struct BulkInstallBarrier {
496 id: u64,
497 reached_rx: watch::Receiver<bool>,
498 release_tx: watch::Sender<bool>,
499 released: bool,
500 }
501
502 impl BulkInstallBarrier {
503 pub(crate) async fn wait_until_reached(&mut self) {
504 if !*self.reached_rx.borrow() {
505 let _ = self.reached_rx.wait_for(|reached| *reached).await;
506 }
507 }
508
509 pub(crate) fn release(&mut self) {
510 if self.released {
511 return;
512 }
513 self.released = true;
514 let _ = self.release_tx.send(true);
515 disarm_barrier(self.id);
516 }
517 }
518
519 impl Drop for BulkInstallBarrier {
520 fn drop(&mut self) {
521 self.release();
522 }
523 }
524
525 pub(crate) fn arm_bulk_install_barrier(
528 target_region_id: RegionId,
529 target_version_control: VersionControlRef,
530 ) -> BulkInstallBarrier {
531 let (reached_tx, reached_rx) = watch::channel(false);
532 let (release_tx, _release_rx) = watch::channel(false);
533 let id = NEXT_BARRIER_ID.fetch_add(1, Ordering::Relaxed);
534 lock_active_barriers().push(ActiveBarrier {
535 id,
536 target_region_id,
537 target_version_control,
538 reached: reached_tx,
539 release: release_tx.clone(),
540 });
541 BulkInstallBarrier {
542 id,
543 reached_rx,
544 release_tx,
545 released: false,
546 }
547 }
548
549 fn disarm_barrier(id: u64) {
550 lock_active_barriers().retain(|barrier| barrier.id != id);
551 }
552
553 pub(crate) async fn pause_before_bulk_install(
556 region_id: RegionId,
557 version_control: &VersionControlRef,
558 ) {
559 let (reached_tx, release_rx) = {
560 let active = lock_active_barriers();
561 let Some(barrier) = active.iter().find(|barrier| {
562 barrier.target_region_id == region_id
563 && std::sync::Arc::ptr_eq(&barrier.target_version_control, version_control)
564 }) else {
565 return;
566 };
567 (barrier.reached.clone(), barrier.release.subscribe())
568 };
569 let _ = reached_tx.send(true);
570 let mut release_rx = release_rx;
571 if !*release_rx.borrow() {
572 let _ = release_rx.wait_for(|released| *released).await;
575 }
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use std::sync::Arc;
582
583 use common_recordbatch::DfRecordBatch;
584 use datatypes::arrow::array::{ArrayRef, TimestampMillisecondArray};
585 use datatypes::arrow::datatypes::{DataType, Field, Schema};
586 use prost::Message;
587 use store_api::logstore::provider::Provider;
588 use tokio::sync::oneshot;
589
590 use super::*;
591 use crate::error::UnexpectedSnafu;
592 use crate::memtable::bulk::part::BulkPart;
593 use crate::test_util::version_util::VersionControlBuilder;
594
595 #[test]
596 fn test_write_notify_dropped_by_panic_reports_error() {
597 let (tx, rx) = oneshot::channel();
598 let notify = WriteNotify::new(OptionOutputTx::from(tx), 3);
599 std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
600 let _notify = notify;
601 panic!("interrupt the write");
602 }))
603 .unwrap_err();
604 assert!(rx.blocking_recv().unwrap().is_err());
605 }
606
607 #[test]
608 fn test_request_skip_wal_preserves_sequences_and_other_writes() {
609 check_request_skip_wal_preserves_sequences_and_other_writes(false, false);
611 check_request_skip_wal_preserves_sequences_and_other_writes(false, true);
612 check_request_skip_wal_preserves_sequences_and_other_writes(true, false);
613 check_request_skip_wal_preserves_sequences_and_other_writes(true, true);
614 }
615
616 fn check_request_skip_wal_preserves_sequences_and_other_writes(
617 skip_wal: bool,
618 bulk_skip_wal: bool,
619 ) {
620 let builder = VersionControlBuilder::new();
621 let region_id = builder.region_id();
622 let version_control = Arc::new(builder.build());
623 let mut ctx = RegionWriteCtx::new(
624 region_id,
625 &version_control,
626 Provider::raft_engine_provider(region_id.as_u64()),
627 None,
628 WriteSource::Request,
629 );
630 for (op_type, skip, num_rows) in [
631 (OpType::Put, skip_wal, 2),
632 (OpType::Put, false, 3),
633 (OpType::Delete, false, 1),
634 ] {
635 ctx.push_mutation(
636 op_type as i32,
637 Some(Rows {
638 schema: vec![],
639 rows: vec![api::v1::Row::default(); num_rows],
640 }),
641 None,
642 OptionOutputTx::none(),
643 None,
644 skip,
645 );
646 }
647 for (mutation, notify) in ctx
649 .wal_entry
650 .mutations
651 .iter()
652 .zip(&ctx.wal_notifiers)
653 .chain(
654 ctx.memtable_mutations
655 .iter()
656 .map(|(mutation, notify)| (mutation, notify)),
657 )
658 {
659 assert_eq!(mutation.rows.as_ref().unwrap().rows.len(), notify.num_rows);
660 }
661 assert!(ctx.push_bulk(OptionOutputTx::none(), new_bulk_part(), None, bulk_skip_wal));
662 assert!(!ctx.skip_wal());
663 assert_eq!(ctx.next_sequence, 9);
664 assert_eq!(
665 ctx.wal_entry.bulk_entries.len(),
666 usize::from(!bulk_skip_wal)
667 );
668 assert_eq!(ctx.bulk_parts[0].sequence, 7);
669 assert_eq!(ctx.bulk_parts[0].min_sequence, 7);
670 let sequences: Vec<_> = ctx.wal_entry.mutations.iter().map(|m| m.sequence).collect();
671 assert_eq!(sequences, if skip_wal { vec![3, 6] } else { vec![1, 3, 6] });
672 let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
674 let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
675 assert_eq!(
676 decoded
677 .mutations
678 .iter()
679 .map(|m| m.sequence)
680 .collect::<Vec<_>>(),
681 sequences
682 );
683 assert_eq!(decoded.bulk_entries, ctx.wal_entry.bulk_entries);
684 assert_eq!(ctx.wal_entry.mutations.len(), if skip_wal { 2 } else { 3 });
685 assert_eq!(ctx.memtable_mutations.len(), usize::from(skip_wal));
686 if skip_wal {
687 assert_eq!(ctx.memtable_mutations[0].0.sequence, 1);
688 }
689 assert_eq!(
690 ctx.wal_entry.mutations.last().unwrap().op_type,
691 OpType::Delete as i32
692 );
693 }
694
695 #[test]
696 fn test_internal_delete_respects_skip_wal_flag() {
697 check_internal_delete_respects_skip_wal_flag(false);
698 check_internal_delete_respects_skip_wal_flag(true);
699 }
700
701 fn check_internal_delete_respects_skip_wal_flag(skip_wal: bool) {
702 let builder = VersionControlBuilder::new();
703 let region_id = builder.region_id();
704 let version_control = Arc::new(builder.build());
705 let mut ctx = RegionWriteCtx::new(
706 region_id,
707 &version_control,
708 Provider::raft_engine_provider(region_id.as_u64()),
709 None,
710 WriteSource::Request,
711 );
712 ctx.push_mutation(
713 OpType::Delete as i32,
714 Some(Rows {
715 schema: vec![],
716 rows: vec![api::v1::Row::default(); 2],
717 }),
718 None,
719 OptionOutputTx::none(),
720 None,
721 skip_wal,
722 );
723 assert_eq!(ctx.skip_wal(), skip_wal);
724 assert_eq!(ctx.next_sequence, 3);
725 assert_eq!(ctx.delete_num, 2);
726 assert_eq!(ctx.wal_entry.mutations.len(), usize::from(!skip_wal));
727 assert_eq!(ctx.memtable_mutations.len(), usize::from(skip_wal));
728 let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
729 let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
730 if skip_wal {
731 assert!(decoded.mutations.is_empty());
732 } else {
733 assert_eq!(decoded, ctx.wal_entry);
734 }
735 }
736
737 #[test]
738 fn test_all_request_skip_wal_keeps_entry_id_and_propagates_errors() {
739 let builder = VersionControlBuilder::new();
740 let region_id = builder.region_id();
741 let version_control = Arc::new(builder.build());
742 let mut ctx = RegionWriteCtx::new(
743 region_id,
744 &version_control,
745 Provider::raft_engine_provider(region_id.as_u64()),
746 None,
747 WriteSource::Request,
748 );
749 let (tx, rx) = oneshot::channel();
750 ctx.push_mutation(
751 OpType::Put as i32,
752 Some(Rows {
753 schema: vec![],
754 rows: vec![api::v1::Row::default(); 2],
755 }),
756 None,
757 OptionOutputTx::from(tx),
758 None,
759 true,
760 );
761 assert!(ctx.skip_wal());
762 assert!(ctx.wal_entry.mutations.is_empty());
763 assert_eq!(ctx.memtable_mutations.len(), 1);
764 assert_eq!(ctx.next_entry_id(), 1);
765 assert_eq!(ctx.next_sequence, 3);
766 ctx.set_error(Arc::new(
767 UnexpectedSnafu {
768 reason: "wal failed".to_string(),
769 }
770 .build(),
771 ));
772 drop(ctx);
773 assert!(rx.blocking_recv().unwrap().is_err());
774 }
775
776 #[test]
777 fn test_bulk_skip_wal_preserves_sequences() {
778 check_bulk_skip_wal_preserves_sequences(false);
779 check_bulk_skip_wal_preserves_sequences(true);
780 }
781
782 fn check_bulk_skip_wal_preserves_sequences(skip_wal: bool) {
783 let builder = VersionControlBuilder::new();
784 let region_id = builder.region_id();
785 let version_control = Arc::new(builder.build());
786 let mut ctx = RegionWriteCtx::new(
787 region_id,
788 &version_control,
789 Provider::raft_engine_provider(region_id.as_u64()),
790 None,
791 WriteSource::Request,
792 );
793 for skip in [skip_wal, false, skip_wal] {
795 assert!(ctx.push_bulk(OptionOutputTx::none(), new_bulk_part(), None, skip));
796 }
797 assert_eq!(ctx.next_sequence, 7);
798 assert_eq!(ctx.bulk_notifiers.len(), 3);
799 assert_eq!(
800 ctx.bulk_parts
801 .iter()
802 .map(|p| p.sequence)
803 .collect::<Vec<_>>(),
804 vec![1, 3, 5]
805 );
806 let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
807 let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
808 assert_eq!(
809 decoded
810 .bulk_entries
811 .iter()
812 .map(|e| e.sequence)
813 .collect::<Vec<_>>(),
814 if skip_wal { vec![3] } else { vec![1, 3, 5] }
815 );
816 }
817
818 #[test]
819 fn test_set_error_marks_bulk_notifiers_failed() {
820 check_set_error_marks_bulk_notifiers_failed(false);
821 check_set_error_marks_bulk_notifiers_failed(true);
822 }
823
824 fn check_set_error_marks_bulk_notifiers_failed(skip_wal: bool) {
825 let builder = VersionControlBuilder::new();
826 let region_id = builder.region_id();
827 let version_control = Arc::new(builder.build());
828 let mut ctx = RegionWriteCtx::new(
829 region_id,
830 &version_control,
831 Provider::noop_provider(),
832 None,
833 WriteSource::Request,
834 );
835 let (tx, rx) = oneshot::channel();
836
837 assert!(ctx.push_bulk(OptionOutputTx::from(tx), new_bulk_part(), None, skip_wal));
838 assert_eq!(ctx.wal_entry.bulk_entries.len(), usize::from(!skip_wal));
839 assert_eq!(ctx.bulk_parts.len(), 1);
840 ctx.set_error(Arc::new(
841 UnexpectedSnafu {
842 reason: "wal failed".to_string(),
843 }
844 .build(),
845 ));
846 drop(ctx);
847
848 let result = rx.blocking_recv().unwrap();
849 assert!(result.is_err(), "bulk notifier should report WAL error");
850 }
851
852 #[tokio::test]
853 async fn test_skip_wal_writes_source_and_policy() {
854 use api::v1::helper::{tag_column_schema, time_index_column_schema};
855 use api::v1::value::ValueData;
856 use api::v1::{ColumnDataType, Row, Value};
857
858 for source in [WriteSource::Request, WriteSource::WalReplay] {
859 for skip_wal in [false, true] {
860 for bulk in [false, true] {
861 for fail_before_install in [false, true] {
862 let builder = VersionControlBuilder::new();
863 let region_id = builder.region_id();
864 let version_control = Arc::new(builder.build());
865 let mut ctx = RegionWriteCtx::new(
866 region_id,
867 &version_control,
868 Provider::raft_engine_provider(region_id.as_u64()),
869 None,
870 source,
871 );
872 let (tx, rx) = oneshot::channel();
873 if bulk {
874 let mut part = new_bulk_part();
875 part.fill_missing_columns(&ctx.version.metadata).unwrap();
876 assert!(ctx.push_bulk(OptionOutputTx::from(tx), part, None, skip_wal));
877 } else {
878 ctx.push_mutation(
879 OpType::Put as i32,
880 Some(Rows {
881 schema: vec![
882 time_index_column_schema(
883 "ts",
884 ColumnDataType::TimestampMillisecond,
885 ),
886 tag_column_schema("tag_0", ColumnDataType::String),
887 ],
888 rows: vec![Row {
889 values: vec![
890 crate::test_util::ts_ms_value(0),
891 Value {
892 value_data: Some(ValueData::StringValue(
893 "a".to_string(),
894 )),
895 },
896 ],
897 }],
898 }),
899 None,
900 OptionOutputTx::from(tx),
901 None,
902 skip_wal,
903 );
904 }
905 if fail_before_install {
906 ctx.set_error(Arc::new(
907 UnexpectedSnafu {
908 reason: "WAL append failed".to_string(),
909 }
910 .build(),
911 ));
912 }
913 ctx.write_memtables().await;
914 assert_eq!(
915 version_control
916 .current()
917 .version
918 .memtables
919 .has_skip_wal_writes(),
920 source == WriteSource::Request && skip_wal && !fail_before_install,
921 "source={source:?}, skip_wal={skip_wal}, bulk={bulk}, failed={fail_before_install}",
922 );
923 assert_eq!(
924 version_control.committed_sequence(),
925 if fail_before_install {
926 0
927 } else if bulk {
928 2
929 } else {
930 1
931 }
932 );
933 drop(ctx);
934 assert_eq!(rx.await.unwrap().is_err(), fail_before_install);
935 }
936 }
937 }
938 }
939 }
940
941 #[test]
942 fn test_skip_wal_writes_region_policy_and_empty_requests() {
943 for source in [WriteSource::Request, WriteSource::WalReplay] {
944 for noop in [false, true] {
945 for region_skip_wal in [false, true] {
946 let builder = VersionControlBuilder::new();
947 let region_id = builder.region_id();
948 let mut version = builder.build_version();
949 version.options.skip_wal = region_skip_wal;
950 let version_control =
951 Arc::new(crate::region::version::VersionControl::new(version));
952 let provider = if noop {
953 Provider::Noop
954 } else {
955 Provider::raft_engine_provider(region_id.as_u64())
956 };
957 let mut ctx =
958 RegionWriteCtx::new(region_id, &version_control, provider, None, source);
959 ctx.push_mutation(
960 OpType::Put as i32,
961 None,
962 None,
963 OptionOutputTx::none(),
964 None,
965 true,
966 );
967 assert!(!ctx.has_skip_wal_writes);
968 ctx.push_mutation(
969 OpType::Put as i32,
970 Some(Rows {
971 schema: vec![],
972 rows: vec![api::v1::Row::default()],
973 }),
974 None,
975 OptionOutputTx::none(),
976 None,
977 false,
978 );
979 assert_eq!(
980 ctx.has_skip_wal_writes,
981 source == WriteSource::Request && (noop || region_skip_wal)
982 );
983 }
984 }
985 }
986 }
987
988 fn new_bulk_part() -> BulkPart {
989 let schema = Arc::new(Schema::new(vec![Field::new(
990 "ts",
991 DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Millisecond, None),
992 false,
993 )]));
994 let arrays = vec![Arc::new(TimestampMillisecondArray::from(vec![1, 2])) as ArrayRef];
995 let batch = DfRecordBatch::try_new(schema, arrays).unwrap();
996
997 BulkPart {
998 batch,
999 max_timestamp: 2,
1000 min_timestamp: 1,
1001 sequence: 0,
1002 min_sequence: 0,
1003 timestamp_index: 0,
1004 raw_data: None,
1005 }
1006 }
1007}