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