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, 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
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        // A panic may have interrupted the write, so it must not be reported as written.
70        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/// Source of writes installed into memtables.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub(crate) enum WriteSource {
85    Request,
86    WalReplay,
87}
88
89/// Context to keep region metadata and buffer write requests.
90pub(crate) struct RegionWriteCtx {
91    /// Source of the buffered writes.
92    source: WriteSource,
93    /// Whether accepted non-empty writes bypass WAL. WAL replay never sets this flag.
94    has_skip_wal_writes: bool,
95    /// Id of region to write.
96    region_id: RegionId,
97    /// Version of the region while creating the context.
98    version: VersionRef,
99    /// VersionControl of the region.
100    version_control: VersionControlRef,
101    /// Next sequence number to write.
102    ///
103    /// The context assigns a unique sequence number for each row.
104    next_sequence: SequenceNumber,
105    /// Next entry id of WAL to write.
106    next_entry_id: EntryId,
107    /// Valid WAL entry to write.
108    ///
109    /// We keep [WalEntry] instead of mutations to avoid taking mutations
110    /// out of the context to construct the wal entry when we write to the wal.
111    wal_entry: WalEntry,
112    /// Mutations that skip WAL, paired with their write notifiers.
113    memtable_mutations: Vec<(Mutation, WriteNotify)>,
114    /// Wal options of the region being written to.
115    provider: Provider,
116    /// Notifiers to send write results to waiters.
117    ///
118    /// The i-th notify is for the i-th mutation in `wal_entry`.
119    wal_notifiers: Vec<WriteNotify>,
120    /// Notifiers for bulk requests.
121    bulk_notifiers: Vec<WriteNotify>,
122    /// Pending bulk write requests
123    pub(crate) bulk_parts: Vec<BulkPart>,
124    /// The write operation is failed and we should not write to the mutable memtable.
125    failed: bool,
126
127    // Metrics:
128    /// Rows to put.
129    pub(crate) put_num: usize,
130    /// Rows to delete.
131    pub(crate) delete_num: usize,
132    /// The total bytes written to the region.
133    pub(crate) written_bytes: Option<Arc<AtomicU64>>,
134}
135
136impl RegionWriteCtx {
137    /// Returns an empty context.
138    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    /// Push mutation to the context.
174    /// This method adopts the sequence number in parameters if present.
175    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        // Assign sequences before routing so concurrent memtable writes retain
197        // their logical order regardless of the WAL policy.
198        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        // Increase sequence number.
207        self.next_sequence += num_rows as u64;
208
209        // Update metrics.
210        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    /// Encode and add WAL entry to the writer.
218    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    /// Returns whether writes in this context should skip WAL.
242    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    /// Sets error and marks all write operations are failed.
249    pub(crate) fn set_error(&mut self, err: Arc<Error>) {
250        // Set error for all notifiers.
251        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        // Fail the whole write operation.
263        self.failed = true;
264    }
265
266    /// Returns whether the write operation is already marked as failed.
267    pub(crate) fn is_failed(&self) -> bool {
268        self.failed
269    }
270
271    /// Updates next entry id.
272    pub(crate) fn set_next_entry_id(&mut self, next_entry_id: EntryId) {
273        self.next_entry_id = next_entry_id
274    }
275
276    /// Returns the next entry id to write.
277    #[cfg(test)]
278    pub(crate) fn next_entry_id(&self) -> EntryId {
279        self.next_entry_id
280    }
281
282    /// Installs ordinary and bulk writes, then publishes their sequence and entry ID.
283    pub(crate) async fn write_memtables(&mut self) {
284        if self.failed {
285            return;
286        }
287        // A failed installation may leave some rows in memory.
288        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    /// Consumes mutations and writes them into mutable memtables.
304    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                    // Keep notifiers in the context until all writes complete.
325                    .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                // use tokio runtime to schedule tasks.
342                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                // First unwrap the result from `spawn` above.
348                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            // first unwrap the result from `spawn` above
431            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    /// Publishes the assigned sequences and entry id to the region's committed
447    /// watermark. Only call after both [`write_mutations`](Self::write_mutations)
448    /// and [`write_bulk`](Self::write_bulk) have completed; a failed context
449    /// must not publish.
450    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/// Test-only hooks to make write ordering races deterministic.
460#[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    /// Channels of an armed bulk-install barrier; dropping the senders (by
471    /// disarming) unblocks writes paused on it.
472    struct ActiveBarrier {
473        id: u64,
474        /// Only bulk writes for this region on this version control (Arc
475        /// identity) pause at the barrier.
476        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        // Never let a poisoned mutex (e.g. a panic in another test while
487        // holding the lock) hang or break unrelated tests.
488        ACTIVE_BARRIERS
489            .lock()
490            .unwrap_or_else(|poisoned| poisoned.into_inner())
491    }
492
493    /// RAII guard: releasing (or dropping) it unblocks a paused write and
494    /// disarms the barrier.
495    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    /// Arms a bulk-install barrier for the target and returns the owning
526    /// guard; barriers of concurrently running tests stay independent.
527    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    /// Pauses a bulk write before installing its parts until the barrier is
554    /// released or disarmed.
555    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            // 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_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        // Ablate only the request flag: the workload and sequence allocation stay identical.
610        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        // Unequal row counts make a notifier/mutation pairing mismatch visible.
648        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        // Check the actual WAL bytes after routing the mutations.
673        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        // Alternate policies in one context, retaining all parts for the memtable.
794        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}