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::MitoRegionRef;
31use crate::region::version::{VersionControlData, VersionControlRef, VersionRef};
32use crate::request::OptionOutputTx;
33use crate::wal::{EntryId, WalWriter};
34
35/// Notifier to notify write result on drop.
36struct WriteNotify {
37    /// Error to send to the waiter.
38    err: Option<Arc<Error>>,
39    /// Sender to send write result to the waiter for this mutation.
40    sender: OptionOutputTx,
41    /// Number of rows to be written.
42    num_rows: usize,
43}
44
45impl WriteNotify {
46    /// Creates a new notify from the `sender`.
47    fn new(sender: OptionOutputTx, num_rows: usize) -> WriteNotify {
48        WriteNotify {
49            err: None,
50            sender,
51            num_rows,
52        }
53    }
54
55    /// Send result to the waiter.
56    fn notify_result(&mut self) {
57        if let Some(err) = &self.err {
58            // Try to send the error to waiters.
59            self.sender
60                .send_mut(Err(err.clone()).context(WriteGroupSnafu));
61        } else {
62            // Send success result.
63            self.sender.send_mut(Ok(self.num_rows));
64        }
65    }
66}
67
68impl Drop for WriteNotify {
69    fn drop(&mut self) {
70        // A panic may have interrupted the write, so it must not be reported as written.
71        if self.err.is_none() && std::thread::panicking() {
72            self.err = Some(Arc::new(
73                UnexpectedSnafu {
74                    reason: "the write was interrupted by a panic",
75                }
76                .build(),
77            ));
78        }
79        self.notify_result();
80    }
81}
82
83/// Source of writes installed into memtables.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub(crate) enum WriteSource {
86    Request,
87    WalReplay,
88}
89
90/// Context to keep region metadata and buffer write requests.
91pub(crate) struct RegionWriteCtx {
92    /// Source of the buffered writes.
93    source: WriteSource,
94    /// Whether accepted non-empty writes bypass WAL. WAL replay never sets this flag.
95    has_skip_wal_writes: bool,
96    /// Id of region to write.
97    region_id: RegionId,
98    /// Version of the region while creating the context.
99    version: VersionRef,
100    /// VersionControl of the region.
101    version_control: VersionControlRef,
102    /// Next sequence number to write.
103    next_sequence: SequenceNumber,
104    /// Maximum sequence number allocated by this context.
105    max_sequence: Option<SequenceNumber>,
106    /// Next entry id of WAL to write.
107    next_entry_id: EntryId,
108    /// Valid WAL entry to write.
109    ///
110    /// We keep [WalEntry] instead of mutations to avoid taking mutations
111    /// out of the context to construct the wal entry when we write to the wal.
112    wal_entry: WalEntry,
113    /// Mutations that skip WAL, paired with their write notifiers.
114    memtable_mutations: Vec<(Mutation, WriteNotify)>,
115    /// Wal options of the region being written to.
116    provider: Provider,
117    /// Notifiers to send write results to waiters.
118    ///
119    /// The i-th notify is for the i-th mutation in `wal_entry`.
120    wal_notifiers: Vec<WriteNotify>,
121    /// Notifiers for bulk requests.
122    bulk_notifiers: Vec<WriteNotify>,
123    /// Pending bulk write requests
124    pub(crate) bulk_parts: Vec<BulkPart>,
125    /// The write operation is failed and we should not write to the mutable memtable.
126    failed: bool,
127
128    // Metrics:
129    /// Rows to put.
130    pub(crate) put_num: usize,
131    /// Rows to delete.
132    pub(crate) delete_num: usize,
133    /// The total bytes written to the region.
134    pub(crate) written_bytes: Option<Arc<AtomicU64>>,
135    /// The region and its Leader deadline when the write was prepared, if the write needs a
136    /// lease.
137    pub(crate) lease: Option<(MitoRegionRef, u64)>,
138}
139
140impl RegionWriteCtx {
141    /// Returns an empty context.
142    pub(crate) fn new(
143        region_id: RegionId,
144        version_control: &VersionControlRef,
145        provider: Provider,
146        written_bytes: Option<Arc<AtomicU64>>,
147        source: WriteSource,
148    ) -> RegionWriteCtx {
149        let VersionControlData {
150            version,
151            last_sequence,
152            last_entry_id,
153            ..
154        } = version_control.current();
155
156        RegionWriteCtx {
157            source,
158            has_skip_wal_writes: false,
159            region_id,
160            version,
161            version_control: version_control.clone(),
162            next_sequence: last_sequence + 1,
163            max_sequence: None,
164            next_entry_id: last_entry_id + 1,
165            wal_entry: WalEntry::default(),
166            memtable_mutations: Vec::new(),
167            provider,
168            wal_notifiers: Vec::new(),
169            bulk_notifiers: vec![],
170            failed: false,
171            put_num: 0,
172            delete_num: 0,
173            bulk_parts: vec![],
174            written_bytes,
175            lease: None,
176        }
177    }
178
179    /// Push mutation to the context.
180    /// This method adopts the sequence number in parameters if present.
181    pub(crate) fn push_mutation(
182        &mut self,
183        op_type: i32,
184        rows: Option<Rows>,
185        write_hint: Option<WriteHint>,
186        tx: OptionOutputTx,
187        sequence: Option<SequenceNumber>,
188        skip_wal: bool,
189    ) {
190        if let Some(sequence) = sequence {
191            self.next_sequence = sequence;
192        }
193        let num_rows = rows.as_ref().map(|rows| rows.rows.len()).unwrap_or(0);
194        self.has_skip_wal_writes |= num_rows > 0 && self.is_skip_wal_write(skip_wal);
195        let mutation = Mutation {
196            op_type,
197            sequence: self.next_sequence,
198            rows,
199            write_hint,
200        };
201
202        // Assign sequences before routing so concurrent memtable writes retain
203        // their logical order regardless of the WAL policy.
204        let notify = WriteNotify::new(tx, num_rows);
205        if skip_wal {
206            self.memtable_mutations.push((mutation, notify));
207        } else {
208            self.wal_entry.mutations.push(mutation);
209            self.wal_notifiers.push(notify);
210        }
211
212        self.advance_sequence(num_rows);
213
214        // Update metrics.
215        match OpType::try_from(op_type) {
216            Ok(OpType::Delete) => self.delete_num += num_rows,
217            Ok(OpType::Put) => self.put_num += num_rows,
218            Err(_) => (),
219        }
220    }
221
222    /// Encode and add WAL entry to the writer.
223    pub(crate) fn add_wal_entry<S: LogStore>(
224        &mut self,
225        wal_writer: &mut WalWriter<S>,
226    ) -> Result<()> {
227        // Reserve sequences before WAL writes to avoid reuse on failure.
228        self.reserve_sequences();
229        wal_writer.add_entry(
230            self.region_id,
231            self.next_entry_id,
232            &self.wal_entry,
233            &self.provider,
234        )?;
235        self.next_entry_id += 1;
236        Ok(())
237    }
238
239    pub(crate) fn version(&self) -> &VersionRef {
240        &self.version
241    }
242
243    #[cfg(test)]
244    pub(crate) fn version_control(&self) -> &VersionControlRef {
245        &self.version_control
246    }
247
248    /// Returns whether writes in this context should skip WAL.
249    pub(crate) fn skip_wal(&self) -> bool {
250        self.provider == Provider::Noop
251            || self.version.options.skip_wal
252            || (self.wal_entry.mutations.is_empty() && self.wal_entry.bulk_entries.is_empty())
253    }
254
255    /// Sets error and marks all write operations are failed.
256    pub(crate) fn set_error(&mut self, err: Arc<Error>) {
257        self.fail_waiters(err);
258
259        // Fail the whole write operation.
260        self.failed = true;
261    }
262
263    /// Sets error for all notifiers. The writes still go to the memtables.
264    pub(crate) fn fail_waiters(&mut self, err: Arc<Error>) {
265        // Set error for all notifiers.
266        for notify in self
267            .wal_notifiers
268            .iter_mut()
269            .chain(self.memtable_mutations.iter_mut().map(|(_, notify)| notify))
270        {
271            notify.err = Some(err.clone());
272        }
273        for notify in &mut self.bulk_notifiers {
274            notify.err = Some(err.clone());
275        }
276    }
277
278    /// Returns whether the write operation is already marked as failed.
279    pub(crate) fn is_failed(&self) -> bool {
280        self.failed
281    }
282
283    /// Updates next entry id.
284    pub(crate) fn set_next_entry_id(&mut self, next_entry_id: EntryId) {
285        self.next_entry_id = next_entry_id
286    }
287
288    /// Returns the next entry id to write.
289    #[cfg(test)]
290    pub(crate) fn next_entry_id(&self) -> EntryId {
291        self.next_entry_id
292    }
293
294    /// Installs ordinary and bulk writes, then publishes their sequence and entry ID.
295    pub(crate) async fn write_memtables(&mut self) {
296        if self.failed {
297            return;
298        }
299        // Reserve sequences for WAL replay and skip-WAL writes.
300        if self.source == WriteSource::WalReplay || self.skip_wal() {
301            self.reserve_sequences();
302        }
303        // A failed installation may leave some rows in memory.
304        if self.has_skip_wal_writes {
305            self.version.memtables.mutable.mark_skip_wal_writes();
306        }
307        self.write_mutations().await;
308        self.write_bulk().await;
309        self.publish_sequence_and_entry_id();
310    }
311
312    fn is_skip_wal_write(&self, request_skip_wal: bool) -> bool {
313        self.source == WriteSource::Request
314            && (request_skip_wal
315                || self.provider == Provider::Noop
316                || self.version.options.skip_wal)
317    }
318
319    /// Consumes mutations and writes them into mutable memtables.
320    async fn write_mutations(&mut self) {
321        debug_assert_eq!(self.wal_notifiers.len(), self.wal_entry.mutations.len());
322
323        if self.failed {
324            return;
325        }
326
327        let mutable_memtable = self.version.memtables.mutable.clone();
328        let prev_memory_usage = if self.written_bytes.is_some() {
329            Some(mutable_memtable.memory_usage())
330        } else {
331            None
332        };
333
334        let mut mutations = mem::take(&mut self.wal_entry.mutations)
335            .into_iter()
336            .zip(&mut self.wal_notifiers)
337            .chain(
338                self.memtable_mutations
339                    .iter_mut()
340                    // Keep notifiers in the context until all writes complete.
341                    .map(|(mutation, notify)| (mem::take(mutation), notify)),
342            )
343            .filter_map(|(mutation, notify)| {
344                let kvs = KeyValues::new(&self.version.metadata, mutation)?;
345                Some((notify, kvs))
346            })
347            .collect::<Vec<_>>();
348
349        if mutations.len() == 1 {
350            if let Err(err) = mutable_memtable.write(&mutations[0].1) {
351                mutations[0].0.err = Some(Arc::new(err));
352            }
353        } else {
354            let mut tasks = FuturesUnordered::new();
355            for (notify, kvs) in mutations {
356                let mutable = mutable_memtable.clone();
357                // use tokio runtime to schedule tasks.
358                let task = common_runtime::spawn_blocking_global(move || mutable.write(&kvs));
359                tasks.push(async move { (notify, task.await) });
360            }
361
362            while let Some((notify, result)) = tasks.next().await {
363                // First unwrap the result from `spawn` above.
364                if let Err(err) = result.unwrap() {
365                    notify.err = Some(Arc::new(err));
366                }
367            }
368        }
369
370        if let Some(written_bytes) = &self.written_bytes {
371            let new_memory_usage = mutable_memtable.memory_usage();
372            let bytes = new_memory_usage.saturating_sub(prev_memory_usage.unwrap_or_default());
373            written_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
374        }
375    }
376
377    pub(crate) fn push_bulk(
378        &mut self,
379        sender: OptionOutputTx,
380        mut bulk: BulkPart,
381        sequence: Option<SequenceNumber>,
382        skip_wal: bool,
383    ) -> bool {
384        if let Some(sequence) = sequence {
385            self.next_sequence = sequence;
386        }
387        bulk.sequence = self.next_sequence;
388        bulk.min_sequence = self.next_sequence;
389        if !skip_wal {
390            let entry = match BulkWalEntry::try_from(&bulk) {
391                Ok(entry) => entry,
392                Err(e) => {
393                    sender.send(Err(e));
394                    return false;
395                }
396            };
397            self.wal_entry.bulk_entries.push(entry);
398        }
399
400        self.has_skip_wal_writes |= bulk.num_rows() > 0 && self.is_skip_wal_write(skip_wal);
401        self.bulk_notifiers
402            .push(WriteNotify::new(sender, bulk.num_rows()));
403
404        self.advance_sequence(bulk.num_rows());
405        self.bulk_parts.push(bulk);
406        true
407    }
408
409    async fn write_bulk(&mut self) {
410        if self.failed || self.bulk_parts.is_empty() {
411            return;
412        }
413        #[cfg(test)]
414        test_hooks::pause_before_bulk_install(self.region_id, &self.version_control).await;
415        let _timer = metrics::REGION_WORKER_HANDLE_WRITE_ELAPSED
416            .with_label_values(&["write_bulk"])
417            .start_timer();
418
419        let mutable_memtable = &self.version.memtables.mutable;
420        let prev_memory_usage = if self.written_bytes.is_some() {
421            Some(mutable_memtable.memory_usage())
422        } else {
423            None
424        };
425
426        if self.bulk_parts.len() == 1 {
427            let part = self.bulk_parts.swap_remove(0);
428            let num_rows = part.num_rows();
429            if let Err(e) = self.version.memtables.mutable.write_bulk(part) {
430                self.bulk_notifiers[0].err = Some(Arc::new(e));
431            } else {
432                self.put_num += num_rows;
433            }
434            return;
435        }
436
437        let mut tasks = FuturesUnordered::new();
438        for (i, part) in self.bulk_parts.drain(..).enumerate() {
439            let mutable = mutable_memtable.clone();
440            tasks.push(common_runtime::spawn_blocking_global(move || {
441                let num_rows = part.num_rows();
442                (i, mutable.write_bulk(part), num_rows)
443            }));
444        }
445        while let Some(result) = tasks.next().await {
446            // first unwrap the result from `spawn` above
447            let (i, result, num_rows) = result.unwrap();
448            if let Err(err) = result {
449                self.bulk_notifiers[i].err = Some(Arc::new(err));
450            } else {
451                self.put_num += num_rows;
452            }
453        }
454
455        if let Some(written_bytes) = &self.written_bytes {
456            let new_memory_usage = mutable_memtable.memory_usage();
457            let bytes = new_memory_usage.saturating_sub(prev_memory_usage.unwrap_or_default());
458            written_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
459        }
460    }
461
462    /// Advances the sequence number and records the maximum.
463    fn advance_sequence(&mut self, num_rows: usize) {
464        self.next_sequence += num_rows as u64;
465        if num_rows > 0 {
466            let sequence = self.next_sequence - 1;
467            self.max_sequence = Some(self.max_sequence.map_or(sequence, |max| max.max(sequence)));
468        }
469    }
470
471    fn reserve_sequences(&self) {
472        if let Some(sequence) = self.max_sequence {
473            self.version_control.update_last_sequence(sequence);
474        }
475    }
476
477    /// Publishes the assigned sequences and entry id to the region's committed
478    /// watermark. Only call after both [`write_mutations`](Self::write_mutations)
479    /// and [`write_bulk`](Self::write_bulk) have completed; a failed context
480    /// must not publish.
481    fn publish_sequence_and_entry_id(&self) {
482        if self.failed {
483            return;
484        }
485        if let Some(sequence) = self.max_sequence {
486            self.version_control
487                .set_sequence_and_entry_id(sequence, self.next_entry_id - 1);
488        } else {
489            // Empty entries only update the entry id.
490            self.version_control.set_entry_id(self.next_entry_id - 1);
491        }
492    }
493}
494
495/// Test-only hooks to make write ordering races deterministic.
496#[cfg(test)]
497pub(crate) mod test_hooks {
498    use std::sync::Mutex;
499    use std::sync::atomic::{AtomicU64, Ordering};
500
501    use store_api::storage::RegionId;
502    use tokio::sync::watch;
503
504    use crate::region::version::VersionControlRef;
505
506    /// Channels of an armed bulk-install barrier; dropping the senders (by
507    /// disarming) unblocks writes paused on it.
508    struct ActiveBarrier {
509        id: u64,
510        /// Only bulk writes for this region on this version control (Arc
511        /// identity) pause at the barrier.
512        target_region_id: RegionId,
513        target_version_control: VersionControlRef,
514        reached: watch::Sender<bool>,
515        release: watch::Sender<bool>,
516    }
517
518    static ACTIVE_BARRIERS: Mutex<Vec<ActiveBarrier>> = Mutex::new(Vec::new());
519    static NEXT_BARRIER_ID: AtomicU64 = AtomicU64::new(1);
520
521    fn lock_active_barriers() -> std::sync::MutexGuard<'static, Vec<ActiveBarrier>> {
522        // Never let a poisoned mutex (e.g. a panic in another test while
523        // holding the lock) hang or break unrelated tests.
524        ACTIVE_BARRIERS
525            .lock()
526            .unwrap_or_else(|poisoned| poisoned.into_inner())
527    }
528
529    /// RAII guard: releasing (or dropping) it unblocks a paused write and
530    /// disarms the barrier.
531    pub(crate) struct BulkInstallBarrier {
532        id: u64,
533        reached_rx: watch::Receiver<bool>,
534        release_tx: watch::Sender<bool>,
535        released: bool,
536    }
537
538    impl BulkInstallBarrier {
539        pub(crate) async fn wait_until_reached(&mut self) {
540            if !*self.reached_rx.borrow() {
541                let _ = self.reached_rx.wait_for(|reached| *reached).await;
542            }
543        }
544
545        pub(crate) fn release(&mut self) {
546            if self.released {
547                return;
548            }
549            self.released = true;
550            let _ = self.release_tx.send(true);
551            disarm_barrier(self.id);
552        }
553    }
554
555    impl Drop for BulkInstallBarrier {
556        fn drop(&mut self) {
557            self.release();
558        }
559    }
560
561    /// Arms a bulk-install barrier for the target and returns the owning
562    /// guard; barriers of concurrently running tests stay independent.
563    pub(crate) fn arm_bulk_install_barrier(
564        target_region_id: RegionId,
565        target_version_control: VersionControlRef,
566    ) -> BulkInstallBarrier {
567        let (reached_tx, reached_rx) = watch::channel(false);
568        let (release_tx, _release_rx) = watch::channel(false);
569        let id = NEXT_BARRIER_ID.fetch_add(1, Ordering::Relaxed);
570        lock_active_barriers().push(ActiveBarrier {
571            id,
572            target_region_id,
573            target_version_control,
574            reached: reached_tx,
575            release: release_tx.clone(),
576        });
577        BulkInstallBarrier {
578            id,
579            reached_rx,
580            release_tx,
581            released: false,
582        }
583    }
584
585    fn disarm_barrier(id: u64) {
586        lock_active_barriers().retain(|barrier| barrier.id != id);
587    }
588
589    /// Pauses a bulk write before installing its parts until the barrier is
590    /// released or disarmed.
591    pub(crate) async fn pause_before_bulk_install(
592        region_id: RegionId,
593        version_control: &VersionControlRef,
594    ) {
595        let (reached_tx, release_rx) = {
596            let active = lock_active_barriers();
597            let Some(barrier) = active.iter().find(|barrier| {
598                barrier.target_region_id == region_id
599                    && std::sync::Arc::ptr_eq(&barrier.target_version_control, version_control)
600            }) else {
601                return;
602            };
603            (barrier.reached.clone(), barrier.release.subscribe())
604        };
605        let _ = reached_tx.send(true);
606        let mut release_rx = release_rx;
607        if !*release_rx.borrow() {
608            // The sender is dropped when the barrier is disarmed, which makes
609            // `wait_for` return an error instead of hanging forever.
610            let _ = release_rx.wait_for(|released| *released).await;
611        }
612    }
613}
614
615#[cfg(test)]
616mod tests {
617    use std::sync::Arc;
618
619    use common_recordbatch::DfRecordBatch;
620    use datatypes::arrow::array::{ArrayRef, TimestampMillisecondArray};
621    use datatypes::arrow::datatypes::{DataType, Field, Schema};
622    use prost::Message;
623    use store_api::logstore::provider::Provider;
624    use tokio::sync::oneshot;
625
626    use super::*;
627    use crate::error::UnexpectedSnafu;
628    use crate::memtable::bulk::part::BulkPart;
629    use crate::test_util::version_util::VersionControlBuilder;
630
631    #[test]
632    fn test_write_notify_dropped_by_panic_reports_error() {
633        let (tx, rx) = oneshot::channel();
634        let notify = WriteNotify::new(OptionOutputTx::from(tx), 3);
635        std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
636            let _notify = notify;
637            panic!("interrupt the write");
638        }))
639        .unwrap_err();
640        assert!(rx.blocking_recv().unwrap().is_err());
641    }
642
643    #[test]
644    fn test_request_skip_wal_preserves_sequences_and_other_writes() {
645        // Ablate only the request flag: the workload and sequence allocation stay identical.
646        check_request_skip_wal_preserves_sequences_and_other_writes(false, false);
647        check_request_skip_wal_preserves_sequences_and_other_writes(false, true);
648        check_request_skip_wal_preserves_sequences_and_other_writes(true, false);
649        check_request_skip_wal_preserves_sequences_and_other_writes(true, true);
650    }
651
652    fn check_request_skip_wal_preserves_sequences_and_other_writes(
653        skip_wal: bool,
654        bulk_skip_wal: bool,
655    ) {
656        let builder = VersionControlBuilder::new();
657        let region_id = builder.region_id();
658        let version_control = Arc::new(builder.build());
659        let mut ctx = RegionWriteCtx::new(
660            region_id,
661            &version_control,
662            Provider::raft_engine_provider(region_id.as_u64()),
663            None,
664            WriteSource::Request,
665        );
666        for (op_type, skip, num_rows) in [
667            (OpType::Put, skip_wal, 2),
668            (OpType::Put, false, 3),
669            (OpType::Delete, false, 1),
670        ] {
671            ctx.push_mutation(
672                op_type as i32,
673                Some(Rows {
674                    schema: vec![],
675                    rows: vec![api::v1::Row::default(); num_rows],
676                }),
677                None,
678                OptionOutputTx::none(),
679                None,
680                skip,
681            );
682        }
683        // Unequal row counts make a notifier/mutation pairing mismatch visible.
684        for (mutation, notify) in ctx
685            .wal_entry
686            .mutations
687            .iter()
688            .zip(&ctx.wal_notifiers)
689            .chain(
690                ctx.memtable_mutations
691                    .iter()
692                    .map(|(mutation, notify)| (mutation, notify)),
693            )
694        {
695            assert_eq!(mutation.rows.as_ref().unwrap().rows.len(), notify.num_rows);
696        }
697        assert!(ctx.push_bulk(OptionOutputTx::none(), new_bulk_part(), None, bulk_skip_wal));
698        assert!(!ctx.skip_wal());
699        assert_eq!(ctx.next_sequence, 9);
700        assert_eq!(
701            ctx.wal_entry.bulk_entries.len(),
702            usize::from(!bulk_skip_wal)
703        );
704        assert_eq!(ctx.bulk_parts[0].sequence, 7);
705        assert_eq!(ctx.bulk_parts[0].min_sequence, 7);
706        let sequences: Vec<_> = ctx.wal_entry.mutations.iter().map(|m| m.sequence).collect();
707        assert_eq!(sequences, if skip_wal { vec![3, 6] } else { vec![1, 3, 6] });
708        // Check the actual WAL bytes after routing the mutations.
709        let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
710        let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
711        assert_eq!(
712            decoded
713                .mutations
714                .iter()
715                .map(|m| m.sequence)
716                .collect::<Vec<_>>(),
717            sequences
718        );
719        assert_eq!(decoded.bulk_entries, ctx.wal_entry.bulk_entries);
720        assert_eq!(ctx.wal_entry.mutations.len(), if skip_wal { 2 } else { 3 });
721        assert_eq!(ctx.memtable_mutations.len(), usize::from(skip_wal));
722        if skip_wal {
723            assert_eq!(ctx.memtable_mutations[0].0.sequence, 1);
724        }
725        assert_eq!(
726            ctx.wal_entry.mutations.last().unwrap().op_type,
727            OpType::Delete as i32
728        );
729    }
730
731    #[test]
732    fn test_internal_delete_respects_skip_wal_flag() {
733        check_internal_delete_respects_skip_wal_flag(false);
734        check_internal_delete_respects_skip_wal_flag(true);
735    }
736
737    fn check_internal_delete_respects_skip_wal_flag(skip_wal: bool) {
738        let builder = VersionControlBuilder::new();
739        let region_id = builder.region_id();
740        let version_control = Arc::new(builder.build());
741        let mut ctx = RegionWriteCtx::new(
742            region_id,
743            &version_control,
744            Provider::raft_engine_provider(region_id.as_u64()),
745            None,
746            WriteSource::Request,
747        );
748        ctx.push_mutation(
749            OpType::Delete as i32,
750            Some(Rows {
751                schema: vec![],
752                rows: vec![api::v1::Row::default(); 2],
753            }),
754            None,
755            OptionOutputTx::none(),
756            None,
757            skip_wal,
758        );
759        assert_eq!(ctx.skip_wal(), skip_wal);
760        assert_eq!(ctx.next_sequence, 3);
761        assert_eq!(ctx.delete_num, 2);
762        assert_eq!(ctx.wal_entry.mutations.len(), usize::from(!skip_wal));
763        assert_eq!(ctx.memtable_mutations.len(), usize::from(skip_wal));
764        let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
765        let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
766        if skip_wal {
767            assert!(decoded.mutations.is_empty());
768        } else {
769            assert_eq!(decoded, ctx.wal_entry);
770        }
771    }
772
773    #[test]
774    fn test_all_request_skip_wal_keeps_entry_id_and_propagates_errors() {
775        let builder = VersionControlBuilder::new();
776        let region_id = builder.region_id();
777        let version_control = Arc::new(builder.build());
778        let mut ctx = RegionWriteCtx::new(
779            region_id,
780            &version_control,
781            Provider::raft_engine_provider(region_id.as_u64()),
782            None,
783            WriteSource::Request,
784        );
785        let (tx, rx) = oneshot::channel();
786        ctx.push_mutation(
787            OpType::Put as i32,
788            Some(Rows {
789                schema: vec![],
790                rows: vec![api::v1::Row::default(); 2],
791            }),
792            None,
793            OptionOutputTx::from(tx),
794            None,
795            true,
796        );
797        assert!(ctx.skip_wal());
798        assert!(ctx.wal_entry.mutations.is_empty());
799        assert_eq!(ctx.memtable_mutations.len(), 1);
800        assert_eq!(ctx.next_entry_id(), 1);
801        assert_eq!(ctx.next_sequence, 3);
802        ctx.set_error(Arc::new(
803            UnexpectedSnafu {
804                reason: "wal failed".to_string(),
805            }
806            .build(),
807        ));
808        drop(ctx);
809        assert!(rx.blocking_recv().unwrap().is_err());
810    }
811
812    #[test]
813    fn test_bulk_skip_wal_preserves_sequences() {
814        check_bulk_skip_wal_preserves_sequences(false);
815        check_bulk_skip_wal_preserves_sequences(true);
816    }
817
818    fn check_bulk_skip_wal_preserves_sequences(skip_wal: bool) {
819        let builder = VersionControlBuilder::new();
820        let region_id = builder.region_id();
821        let version_control = Arc::new(builder.build());
822        let mut ctx = RegionWriteCtx::new(
823            region_id,
824            &version_control,
825            Provider::raft_engine_provider(region_id.as_u64()),
826            None,
827            WriteSource::Request,
828        );
829        // Alternate policies in one context, retaining all parts for the memtable.
830        for skip in [skip_wal, false, skip_wal] {
831            assert!(ctx.push_bulk(OptionOutputTx::none(), new_bulk_part(), None, skip));
832        }
833        assert_eq!(ctx.next_sequence, 7);
834        assert_eq!(ctx.bulk_notifiers.len(), 3);
835        assert_eq!(
836            ctx.bulk_parts
837                .iter()
838                .map(|p| p.sequence)
839                .collect::<Vec<_>>(),
840            vec![1, 3, 5]
841        );
842        let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
843        let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
844        assert_eq!(
845            decoded
846                .bulk_entries
847                .iter()
848                .map(|e| e.sequence)
849                .collect::<Vec<_>>(),
850            if skip_wal { vec![3] } else { vec![1, 3, 5] }
851        );
852    }
853
854    #[test]
855    fn test_set_error_marks_bulk_notifiers_failed() {
856        check_set_error_marks_bulk_notifiers_failed(false);
857        check_set_error_marks_bulk_notifiers_failed(true);
858    }
859
860    fn check_set_error_marks_bulk_notifiers_failed(skip_wal: bool) {
861        let builder = VersionControlBuilder::new();
862        let region_id = builder.region_id();
863        let version_control = Arc::new(builder.build());
864        let mut ctx = RegionWriteCtx::new(
865            region_id,
866            &version_control,
867            Provider::noop_provider(),
868            None,
869            WriteSource::Request,
870        );
871        let (tx, rx) = oneshot::channel();
872
873        assert!(ctx.push_bulk(OptionOutputTx::from(tx), new_bulk_part(), None, skip_wal));
874        assert_eq!(ctx.wal_entry.bulk_entries.len(), usize::from(!skip_wal));
875        assert_eq!(ctx.bulk_parts.len(), 1);
876        ctx.set_error(Arc::new(
877            UnexpectedSnafu {
878                reason: "wal failed".to_string(),
879            }
880            .build(),
881        ));
882        drop(ctx);
883
884        let result = rx.blocking_recv().unwrap();
885        assert!(result.is_err(), "bulk notifier should report WAL error");
886    }
887
888    #[tokio::test]
889    async fn test_skip_wal_writes_source_and_policy() {
890        use api::v1::helper::{tag_column_schema, time_index_column_schema};
891        use api::v1::value::ValueData;
892        use api::v1::{ColumnDataType, Row, Value};
893
894        for source in [WriteSource::Request, WriteSource::WalReplay] {
895            for skip_wal in [false, true] {
896                for bulk in [false, true] {
897                    for fail_before_install in [false, true] {
898                        let builder = VersionControlBuilder::new();
899                        let region_id = builder.region_id();
900                        let version_control = Arc::new(builder.build());
901                        let mut ctx = RegionWriteCtx::new(
902                            region_id,
903                            &version_control,
904                            Provider::raft_engine_provider(region_id.as_u64()),
905                            None,
906                            source,
907                        );
908                        let (tx, rx) = oneshot::channel();
909                        if bulk {
910                            let mut part = new_bulk_part();
911                            part.fill_missing_columns(&ctx.version.metadata).unwrap();
912                            assert!(ctx.push_bulk(OptionOutputTx::from(tx), part, None, skip_wal));
913                        } else {
914                            ctx.push_mutation(
915                                OpType::Put as i32,
916                                Some(Rows {
917                                    schema: vec![
918                                        time_index_column_schema(
919                                            "ts",
920                                            ColumnDataType::TimestampMillisecond,
921                                        ),
922                                        tag_column_schema("tag_0", ColumnDataType::String),
923                                    ],
924                                    rows: vec![Row {
925                                        values: vec![
926                                            crate::test_util::ts_ms_value(0),
927                                            Value {
928                                                value_data: Some(ValueData::StringValue(
929                                                    "a".to_string(),
930                                                )),
931                                            },
932                                        ],
933                                    }],
934                                }),
935                                None,
936                                OptionOutputTx::from(tx),
937                                None,
938                                skip_wal,
939                            );
940                        }
941                        if fail_before_install {
942                            ctx.set_error(Arc::new(
943                                UnexpectedSnafu {
944                                    reason: "WAL append failed".to_string(),
945                                }
946                                .build(),
947                            ));
948                        }
949                        ctx.write_memtables().await;
950                        assert_eq!(
951                            version_control
952                                .current()
953                                .version
954                                .memtables
955                                .has_skip_wal_writes(),
956                            source == WriteSource::Request && skip_wal && !fail_before_install,
957                            "source={source:?}, skip_wal={skip_wal}, bulk={bulk}, failed={fail_before_install}",
958                        );
959                        assert_eq!(
960                            version_control.committed_sequence(),
961                            if fail_before_install {
962                                0
963                            } else if bulk {
964                                2
965                            } else {
966                                1
967                            }
968                        );
969                        drop(ctx);
970                        assert_eq!(rx.await.unwrap().is_err(), fail_before_install);
971                    }
972                }
973            }
974        }
975    }
976
977    #[test]
978    fn test_skip_wal_writes_region_policy_and_empty_requests() {
979        for source in [WriteSource::Request, WriteSource::WalReplay] {
980            for noop in [false, true] {
981                for region_skip_wal in [false, true] {
982                    let builder = VersionControlBuilder::new();
983                    let region_id = builder.region_id();
984                    let mut version = builder.build_version();
985                    version.options.skip_wal = region_skip_wal;
986                    let version_control =
987                        Arc::new(crate::region::version::VersionControl::new(version));
988                    let provider = if noop {
989                        Provider::Noop
990                    } else {
991                        Provider::raft_engine_provider(region_id.as_u64())
992                    };
993                    let mut ctx =
994                        RegionWriteCtx::new(region_id, &version_control, provider, None, source);
995                    ctx.push_mutation(
996                        OpType::Put as i32,
997                        None,
998                        None,
999                        OptionOutputTx::none(),
1000                        None,
1001                        true,
1002                    );
1003                    assert!(!ctx.has_skip_wal_writes);
1004                    ctx.push_mutation(
1005                        OpType::Put as i32,
1006                        Some(Rows {
1007                            schema: vec![],
1008                            rows: vec![api::v1::Row::default()],
1009                        }),
1010                        None,
1011                        OptionOutputTx::none(),
1012                        None,
1013                        false,
1014                    );
1015                    assert_eq!(
1016                        ctx.has_skip_wal_writes,
1017                        source == WriteSource::Request && (noop || region_skip_wal)
1018                    );
1019                }
1020            }
1021        }
1022    }
1023
1024    fn new_bulk_part() -> BulkPart {
1025        let schema = Arc::new(Schema::new(vec![Field::new(
1026            "ts",
1027            DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Millisecond, None),
1028            false,
1029        )]));
1030        let arrays = vec![Arc::new(TimestampMillisecondArray::from(vec![1, 2])) as ArrayRef];
1031        let batch = DfRecordBatch::try_new(schema, arrays).unwrap();
1032
1033        BulkPart {
1034            batch,
1035            max_timestamp: 2,
1036            min_timestamp: 1,
1037            sequence: 0,
1038            min_sequence: 0,
1039            timestamp_index: 0,
1040            raw_data: None,
1041        }
1042    }
1043
1044    fn sequence_rows(num_rows: usize) -> Rows {
1045        use api::v1::helper::{tag_column_schema, time_index_column_schema};
1046        Rows {
1047            schema: vec![
1048                time_index_column_schema("ts", api::v1::ColumnDataType::TimestampMillisecond),
1049                tag_column_schema("tag_0", api::v1::ColumnDataType::String),
1050            ],
1051            rows: (0..num_rows)
1052                .map(|i| api::v1::Row {
1053                    values: vec![
1054                        crate::test_util::ts_ms_value(i as i64),
1055                        api::v1::Value {
1056                            value_data: Some(api::v1::value::ValueData::StringValue(
1057                                "a".to_string(),
1058                            )),
1059                        },
1060                    ],
1061                })
1062                .collect(),
1063        }
1064    }
1065
1066    #[tokio::test]
1067    async fn test_replay_sequence_maximum_below_allocation_floor() {
1068        let builder = VersionControlBuilder::new();
1069        let region_id = builder.region_id();
1070        let vc = Arc::new(builder.build());
1071        vc.update_last_sequence(100);
1072        let provider = Provider::raft_engine_provider(region_id.as_u64());
1073        let mut ctx = RegionWriteCtx::new(
1074            region_id,
1075            &vc,
1076            provider.clone(),
1077            None,
1078            WriteSource::WalReplay,
1079        );
1080        for (sequence, num_rows) in [(40, 2), (3, 1)] {
1081            ctx.push_mutation(
1082                OpType::Put as i32,
1083                Some(sequence_rows(num_rows)),
1084                None,
1085                OptionOutputTx::none(),
1086                Some(sequence),
1087                false,
1088            );
1089        }
1090        for sequence in [10, 2] {
1091            let mut part = new_bulk_part();
1092            part.fill_missing_columns(&ctx.version.metadata).unwrap();
1093            assert!(ctx.push_bulk(OptionOutputTx::none(), part, Some(sequence), false));
1094        }
1095        assert_eq!(ctx.wal_entry.mutations[0].sequence, 40);
1096        assert_eq!(ctx.bulk_parts[0].sequence, 10);
1097        assert_eq!(ctx.bulk_parts[1].sequence, 2);
1098        assert_eq!(vc.committed_sequence(), 0);
1099        ctx.set_next_entry_id(8);
1100        ctx.write_memtables().await;
1101        assert_eq!(vc.committed_sequence(), 41);
1102        assert_eq!(vc.current().last_sequence, 100);
1103        assert_eq!(vc.current().last_entry_id, 7);
1104
1105        let mut ctx = RegionWriteCtx::new(region_id, &vc, provider, None, WriteSource::Request);
1106        ctx.push_mutation(
1107            OpType::Put as i32,
1108            Some(sequence_rows(2)),
1109            None,
1110            OptionOutputTx::none(),
1111            None,
1112            true,
1113        );
1114        assert_eq!(ctx.memtable_mutations[0].0.sequence, 101);
1115        ctx.write_memtables().await;
1116        assert_eq!(vc.committed_sequence(), 102);
1117        assert_eq!(vc.current().last_sequence, 102);
1118        assert_eq!(vc.current().last_entry_id, 7);
1119    }
1120
1121    #[tokio::test]
1122    async fn test_empty_context_does_not_publish_allocation_floor() {
1123        let builder = VersionControlBuilder::new();
1124        let region_id = builder.region_id();
1125        let vc = Arc::new(builder.build());
1126        vc.update_last_sequence(100);
1127        for empty_objects in [false, true] {
1128            let mut ctx =
1129                RegionWriteCtx::new(region_id, &vc, Provider::Noop, None, WriteSource::WalReplay);
1130            if empty_objects {
1131                ctx.push_mutation(
1132                    OpType::Put as i32,
1133                    Some(sequence_rows(0)),
1134                    None,
1135                    OptionOutputTx::none(),
1136                    Some(500),
1137                    false,
1138                );
1139                ctx.push_mutation(
1140                    OpType::Put as i32,
1141                    None,
1142                    None,
1143                    OptionOutputTx::none(),
1144                    Some(600),
1145                    false,
1146                );
1147                let mut part = new_bulk_part();
1148                part.fill_missing_columns(&ctx.version.metadata).unwrap();
1149                part.batch = part.batch.slice(0, 0);
1150                assert!(ctx.push_bulk(OptionOutputTx::none(), part, Some(700), true));
1151            }
1152            ctx.set_next_entry_id(4);
1153            ctx.write_memtables().await;
1154            let current = vc.current();
1155            assert_eq!(current.last_sequence, 100);
1156            assert_eq!(current.committed_sequence, 0);
1157            assert_eq!(current.last_entry_id, 3);
1158        }
1159    }
1160
1161    #[tokio::test]
1162    async fn test_rejected_bulk_does_not_publish_sequence() {
1163        use datatypes::arrow::array::StringDictionaryBuilder;
1164        use datatypes::arrow::datatypes::Int32Type;
1165
1166        let builder = VersionControlBuilder::new();
1167        let vc = Arc::new(builder.build());
1168        vc.update_last_sequence(100);
1169        let mut ctx = RegionWriteCtx::new(
1170            builder.region_id(),
1171            &vc,
1172            Provider::Noop,
1173            None,
1174            WriteSource::Request,
1175        );
1176        let mut part = new_bulk_part();
1177        let mut dictionary = StringDictionaryBuilder::<Int32Type>::new();
1178        dictionary.append("a").unwrap();
1179        dictionary.append("b").unwrap();
1180        let dictionary = Arc::new(dictionary.finish()) as ArrayRef;
1181        let mut fields = part.batch.schema().fields().to_vec();
1182        fields.push(Arc::new(Field::new(
1183            "tag_0",
1184            dictionary.data_type().clone(),
1185            true,
1186        )));
1187        part.batch = DfRecordBatch::try_new(
1188            Arc::new(Schema::new(fields)),
1189            vec![part.batch.column(0).clone(), dictionary],
1190        )
1191        .unwrap();
1192        let (tx, rx) = oneshot::channel();
1193        assert!(!ctx.push_bulk(OptionOutputTx::from(tx), part, Some(500), false));
1194        assert!(rx.await.unwrap().is_err());
1195        ctx.write_memtables().await;
1196        assert_eq!(vc.committed_sequence(), 0);
1197        assert_eq!(vc.current().last_sequence, 100);
1198    }
1199}