Skip to main content

mito2/
region_write_ctx.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
34/// Notifier to notify write result on drop.
35struct WriteNotify {
36    /// Error to send to the waiter.
37    err: Option<Arc<Error>>,
38    /// Sender to send write result to the waiter for this mutation.
39    sender: OptionOutputTx,
40    /// Number of rows to be written.
41    num_rows: usize,
42}
43
44impl WriteNotify {
45    /// Creates a new notify from the `sender`.
46    fn new(sender: OptionOutputTx, num_rows: usize) -> WriteNotify {
47        WriteNotify {
48            err: None,
49            sender,
50            num_rows,
51        }
52    }
53
54    /// Send result to the waiter.
55    fn notify_result(&mut self) {
56        if let Some(err) = &self.err {
57            // Try to send the error to waiters.
58            self.sender
59                .send_mut(Err(err.clone()).context(WriteGroupSnafu));
60        } else {
61            // Send success result.
62            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/// Source of writes installed into memtables.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub(crate) enum WriteSource {
76    Request,
77    WalReplay,
78}
79
80/// Context to keep region metadata and buffer write requests.
81pub(crate) struct RegionWriteCtx {
82    /// Source of the buffered writes.
83    source: WriteSource,
84    /// Whether accepted non-empty writes bypass WAL. WAL replay never sets this flag.
85    has_skip_wal_writes: bool,
86    /// Id of region to write.
87    region_id: RegionId,
88    /// Version of the region while creating the context.
89    version: VersionRef,
90    /// VersionControl of the region.
91    version_control: VersionControlRef,
92    /// Next sequence number to write.
93    ///
94    /// The context assigns a unique sequence number for each row.
95    next_sequence: SequenceNumber,
96    /// Next entry id of WAL to write.
97    next_entry_id: EntryId,
98    /// Valid WAL entry to write.
99    ///
100    /// We keep [WalEntry] instead of mutations to avoid taking mutations
101    /// out of the context to construct the wal entry when we write to the wal.
102    wal_entry: WalEntry,
103    /// Mutations that skip WAL, paired with their write notifiers.
104    memtable_mutations: Vec<(Mutation, WriteNotify)>,
105    /// Wal options of the region being written to.
106    provider: Provider,
107    /// Notifiers to send write results to waiters.
108    ///
109    /// The i-th notify is for the i-th mutation in `wal_entry`.
110    wal_notifiers: Vec<WriteNotify>,
111    /// Notifiers for bulk requests.
112    bulk_notifiers: Vec<WriteNotify>,
113    /// Pending bulk write requests
114    pub(crate) bulk_parts: Vec<BulkPart>,
115    /// The write operation is failed and we should not write to the mutable memtable.
116    failed: bool,
117
118    // Metrics:
119    /// Rows to put.
120    pub(crate) put_num: usize,
121    /// Rows to delete.
122    pub(crate) delete_num: usize,
123    /// The total bytes written to the region.
124    pub(crate) written_bytes: Option<Arc<AtomicU64>>,
125}
126
127impl RegionWriteCtx {
128    /// Returns an empty context.
129    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    /// Push mutation to the context.
165    /// This method adopts the sequence number in parameters if present.
166    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        // Assign sequences before routing so concurrent memtable writes retain
188        // their logical order regardless of the WAL policy.
189        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        // Increase sequence number.
198        self.next_sequence += num_rows as u64;
199
200        // Update metrics.
201        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    /// Encode and add WAL entry to the writer.
209    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    /// Returns whether writes in this context should skip WAL.
233    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    /// Sets error and marks all write operations are failed.
240    pub(crate) fn set_error(&mut self, err: Arc<Error>) {
241        // Set error for all notifiers.
242        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        // Fail the whole write operation.
254        self.failed = true;
255    }
256
257    /// Returns whether the write operation is already marked as failed.
258    pub(crate) fn is_failed(&self) -> bool {
259        self.failed
260    }
261
262    /// Updates next entry id.
263    pub(crate) fn set_next_entry_id(&mut self, next_entry_id: EntryId) {
264        self.next_entry_id = next_entry_id
265    }
266
267    /// Returns the next entry id to write.
268    #[cfg(test)]
269    pub(crate) fn next_entry_id(&self) -> EntryId {
270        self.next_entry_id
271    }
272
273    /// Installs ordinary and bulk writes, then publishes their sequence and entry ID.
274    pub(crate) async fn write_memtables(&mut self) {
275        if self.failed {
276            return;
277        }
278        // A failed installation may leave some rows in memory.
279        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    /// Consumes mutations and writes them into mutable memtables.
295    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                    // Keep notifiers in the context until all writes complete.
316                    .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                // use tokio runtime to schedule tasks.
333                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                // First unwrap the result from `spawn` above.
339                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            // first unwrap the result from `spawn` above
422            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    /// Publishes the assigned sequences and entry id to the region's committed
438    /// watermark. Only call after both [`write_mutations`](Self::write_mutations)
439    /// and [`write_bulk`](Self::write_bulk) have completed; a failed context
440    /// must not publish.
441    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/// Test-only hooks to make write ordering races deterministic.
451#[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    /// Channels of an armed bulk-install barrier; dropping the senders (by
462    /// disarming) unblocks writes paused on it.
463    struct ActiveBarrier {
464        id: u64,
465        /// Only bulk writes for this region on this version control (Arc
466        /// identity) pause at the barrier.
467        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        // Never let a poisoned mutex (e.g. a panic in another test while
478        // holding the lock) hang or break unrelated tests.
479        ACTIVE_BARRIER
480            .lock()
481            .unwrap_or_else(|poisoned| poisoned.into_inner())
482    }
483
484    /// RAII guard: releasing (or dropping) it unblocks a paused write and
485    /// disarms the barrier.
486    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    /// Arms the bulk-install barrier and returns the owning guard; any
517    /// previously armed barrier is replaced.
518    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    /// Pauses a bulk write before installing its parts until the barrier is
549    /// released or disarmed.
550    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            // The sender is dropped when the barrier is disarmed, which makes
573            // `wait_for` return an error instead of hanging forever.
574            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        // Ablate only the request flag: the workload and sequence allocation stay identical.
598        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        // Unequal row counts make a notifier/mutation pairing mismatch visible.
636        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        // Check the actual WAL bytes after routing the mutations.
661        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        // Alternate policies in one context, retaining all parts for the memtable.
782        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}