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        // WriteAttribution requires the input count on success, before any deduplication.
402        self.bulk_notifiers
403            .push(WriteNotify::new(sender, bulk.num_rows()));
404
405        self.advance_sequence(bulk.num_rows());
406        self.bulk_parts.push(bulk);
407        true
408    }
409
410    async fn write_bulk(&mut self) {
411        if self.failed || self.bulk_parts.is_empty() {
412            return;
413        }
414        #[cfg(test)]
415        test_hooks::pause_before_bulk_install(self.region_id, &self.version_control).await;
416        let _timer = metrics::REGION_WORKER_HANDLE_WRITE_ELAPSED
417            .with_label_values(&["write_bulk"])
418            .start_timer();
419
420        let mutable_memtable = &self.version.memtables.mutable;
421        let prev_memory_usage = if self.written_bytes.is_some() {
422            Some(mutable_memtable.memory_usage())
423        } else {
424            None
425        };
426
427        if self.bulk_parts.len() == 1 {
428            let part = self.bulk_parts.swap_remove(0);
429            let num_rows = part.num_rows();
430            if let Err(e) = self.version.memtables.mutable.write_bulk(part) {
431                self.bulk_notifiers[0].err = Some(Arc::new(e));
432            } else {
433                self.put_num += num_rows;
434            }
435            return;
436        }
437
438        let mut tasks = FuturesUnordered::new();
439        for (i, part) in self.bulk_parts.drain(..).enumerate() {
440            let mutable = mutable_memtable.clone();
441            tasks.push(common_runtime::spawn_blocking_global(move || {
442                let num_rows = part.num_rows();
443                (i, mutable.write_bulk(part), num_rows)
444            }));
445        }
446        while let Some(result) = tasks.next().await {
447            // first unwrap the result from `spawn` above
448            let (i, result, num_rows) = result.unwrap();
449            if let Err(err) = result {
450                self.bulk_notifiers[i].err = Some(Arc::new(err));
451            } else {
452                self.put_num += num_rows;
453            }
454        }
455
456        if let Some(written_bytes) = &self.written_bytes {
457            let new_memory_usage = mutable_memtable.memory_usage();
458            let bytes = new_memory_usage.saturating_sub(prev_memory_usage.unwrap_or_default());
459            written_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
460        }
461    }
462
463    /// Advances the sequence number and records the maximum.
464    fn advance_sequence(&mut self, num_rows: usize) {
465        self.next_sequence += num_rows as u64;
466        if num_rows > 0 {
467            let sequence = self.next_sequence - 1;
468            self.max_sequence = Some(self.max_sequence.map_or(sequence, |max| max.max(sequence)));
469        }
470    }
471
472    fn reserve_sequences(&self) {
473        if let Some(sequence) = self.max_sequence {
474            self.version_control.update_last_sequence(sequence);
475        }
476    }
477
478    /// Publishes the assigned sequences and entry id to the region's committed
479    /// watermark. Only call after both [`write_mutations`](Self::write_mutations)
480    /// and [`write_bulk`](Self::write_bulk) have completed; a failed context
481    /// must not publish.
482    fn publish_sequence_and_entry_id(&self) {
483        if self.failed {
484            return;
485        }
486        if let Some(sequence) = self.max_sequence {
487            self.version_control
488                .set_sequence_and_entry_id(sequence, self.next_entry_id - 1);
489        } else {
490            // Empty entries only update the entry id.
491            self.version_control.set_entry_id(self.next_entry_id - 1);
492        }
493    }
494}
495
496/// Test-only hooks to make write ordering races deterministic.
497#[cfg(test)]
498pub(crate) mod test_hooks {
499    use std::sync::Mutex;
500    use std::sync::atomic::{AtomicU64, Ordering};
501
502    use store_api::storage::RegionId;
503    use tokio::sync::watch;
504
505    use crate::region::version::VersionControlRef;
506
507    /// Channels of an armed bulk-install barrier; dropping the senders (by
508    /// disarming) unblocks writes paused on it.
509    struct ActiveBarrier {
510        id: u64,
511        /// Only bulk writes for this region on this version control (Arc
512        /// identity) pause at the barrier.
513        target_region_id: RegionId,
514        target_version_control: VersionControlRef,
515        reached: watch::Sender<bool>,
516        release: watch::Sender<bool>,
517    }
518
519    static ACTIVE_BARRIERS: Mutex<Vec<ActiveBarrier>> = Mutex::new(Vec::new());
520    static NEXT_BARRIER_ID: AtomicU64 = AtomicU64::new(1);
521
522    fn lock_active_barriers() -> std::sync::MutexGuard<'static, Vec<ActiveBarrier>> {
523        // Never let a poisoned mutex (e.g. a panic in another test while
524        // holding the lock) hang or break unrelated tests.
525        ACTIVE_BARRIERS
526            .lock()
527            .unwrap_or_else(|poisoned| poisoned.into_inner())
528    }
529
530    /// RAII guard: releasing (or dropping) it unblocks a paused write and
531    /// disarms the barrier.
532    pub(crate) struct BulkInstallBarrier {
533        id: u64,
534        reached_rx: watch::Receiver<bool>,
535        release_tx: watch::Sender<bool>,
536        released: bool,
537    }
538
539    impl BulkInstallBarrier {
540        pub(crate) async fn wait_until_reached(&mut self) {
541            if !*self.reached_rx.borrow() {
542                let _ = self.reached_rx.wait_for(|reached| *reached).await;
543            }
544        }
545
546        pub(crate) fn release(&mut self) {
547            if self.released {
548                return;
549            }
550            self.released = true;
551            let _ = self.release_tx.send(true);
552            disarm_barrier(self.id);
553        }
554    }
555
556    impl Drop for BulkInstallBarrier {
557        fn drop(&mut self) {
558            self.release();
559        }
560    }
561
562    /// Arms a bulk-install barrier for the target and returns the owning
563    /// guard; barriers of concurrently running tests stay independent.
564    pub(crate) fn arm_bulk_install_barrier(
565        target_region_id: RegionId,
566        target_version_control: VersionControlRef,
567    ) -> BulkInstallBarrier {
568        let (reached_tx, reached_rx) = watch::channel(false);
569        let (release_tx, _release_rx) = watch::channel(false);
570        let id = NEXT_BARRIER_ID.fetch_add(1, Ordering::Relaxed);
571        lock_active_barriers().push(ActiveBarrier {
572            id,
573            target_region_id,
574            target_version_control,
575            reached: reached_tx,
576            release: release_tx.clone(),
577        });
578        BulkInstallBarrier {
579            id,
580            reached_rx,
581            release_tx,
582            released: false,
583        }
584    }
585
586    fn disarm_barrier(id: u64) {
587        lock_active_barriers().retain(|barrier| barrier.id != id);
588    }
589
590    /// Pauses a bulk write before installing its parts until the barrier is
591    /// released or disarmed.
592    pub(crate) async fn pause_before_bulk_install(
593        region_id: RegionId,
594        version_control: &VersionControlRef,
595    ) {
596        let (reached_tx, release_rx) = {
597            let active = lock_active_barriers();
598            let Some(barrier) = active.iter().find(|barrier| {
599                barrier.target_region_id == region_id
600                    && std::sync::Arc::ptr_eq(&barrier.target_version_control, version_control)
601            }) else {
602                return;
603            };
604            (barrier.reached.clone(), barrier.release.subscribe())
605        };
606        let _ = reached_tx.send(true);
607        let mut release_rx = release_rx;
608        if !*release_rx.borrow() {
609            // The sender is dropped when the barrier is disarmed, which makes
610            // `wait_for` return an error instead of hanging forever.
611            let _ = release_rx.wait_for(|released| *released).await;
612        }
613    }
614}
615
616#[cfg(test)]
617mod tests {
618    use std::sync::Arc;
619
620    use common_recordbatch::DfRecordBatch;
621    use datatypes::arrow::array::{ArrayRef, TimestampMillisecondArray};
622    use datatypes::arrow::datatypes::{DataType, Field, Schema};
623    use prost::Message;
624    use store_api::logstore::provider::Provider;
625    use tokio::sync::oneshot;
626
627    use super::*;
628    use crate::error::UnexpectedSnafu;
629    use crate::memtable::bulk::part::BulkPart;
630    use crate::test_util::version_util::VersionControlBuilder;
631
632    #[test]
633    fn test_write_notify_dropped_by_panic_reports_error() {
634        let (tx, rx) = oneshot::channel();
635        let notify = WriteNotify::new(OptionOutputTx::from(tx), 3);
636        std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
637            let _notify = notify;
638            panic!("interrupt the write");
639        }))
640        .unwrap_err();
641        assert!(rx.blocking_recv().unwrap().is_err());
642    }
643
644    #[test]
645    fn test_request_skip_wal_preserves_sequences_and_other_writes() {
646        // Ablate only the request flag: the workload and sequence allocation stay identical.
647        check_request_skip_wal_preserves_sequences_and_other_writes(false, false);
648        check_request_skip_wal_preserves_sequences_and_other_writes(false, true);
649        check_request_skip_wal_preserves_sequences_and_other_writes(true, false);
650        check_request_skip_wal_preserves_sequences_and_other_writes(true, true);
651    }
652
653    fn check_request_skip_wal_preserves_sequences_and_other_writes(
654        skip_wal: bool,
655        bulk_skip_wal: bool,
656    ) {
657        let builder = VersionControlBuilder::new();
658        let region_id = builder.region_id();
659        let version_control = Arc::new(builder.build());
660        let mut ctx = RegionWriteCtx::new(
661            region_id,
662            &version_control,
663            Provider::raft_engine_provider(region_id.as_u64()),
664            None,
665            WriteSource::Request,
666        );
667        for (op_type, skip, num_rows) in [
668            (OpType::Put, skip_wal, 2),
669            (OpType::Put, false, 3),
670            (OpType::Delete, false, 1),
671        ] {
672            ctx.push_mutation(
673                op_type as i32,
674                Some(Rows {
675                    schema: vec![],
676                    rows: vec![api::v1::Row::default(); num_rows],
677                }),
678                None,
679                OptionOutputTx::none(),
680                None,
681                skip,
682            );
683        }
684        // Unequal row counts make a notifier/mutation pairing mismatch visible.
685        for (mutation, notify) in ctx
686            .wal_entry
687            .mutations
688            .iter()
689            .zip(&ctx.wal_notifiers)
690            .chain(
691                ctx.memtable_mutations
692                    .iter()
693                    .map(|(mutation, notify)| (mutation, notify)),
694            )
695        {
696            assert_eq!(mutation.rows.as_ref().unwrap().rows.len(), notify.num_rows);
697        }
698        assert!(ctx.push_bulk(OptionOutputTx::none(), new_bulk_part(), None, bulk_skip_wal));
699        assert!(!ctx.skip_wal());
700        assert_eq!(ctx.next_sequence, 9);
701        assert_eq!(
702            ctx.wal_entry.bulk_entries.len(),
703            usize::from(!bulk_skip_wal)
704        );
705        assert_eq!(ctx.bulk_parts[0].sequence, 7);
706        assert_eq!(ctx.bulk_parts[0].min_sequence, 7);
707        let sequences: Vec<_> = ctx.wal_entry.mutations.iter().map(|m| m.sequence).collect();
708        assert_eq!(sequences, if skip_wal { vec![3, 6] } else { vec![1, 3, 6] });
709        // Check the actual WAL bytes after routing the mutations.
710        let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
711        let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
712        assert_eq!(
713            decoded
714                .mutations
715                .iter()
716                .map(|m| m.sequence)
717                .collect::<Vec<_>>(),
718            sequences
719        );
720        assert_eq!(decoded.bulk_entries, ctx.wal_entry.bulk_entries);
721        assert_eq!(ctx.wal_entry.mutations.len(), if skip_wal { 2 } else { 3 });
722        assert_eq!(ctx.memtable_mutations.len(), usize::from(skip_wal));
723        if skip_wal {
724            assert_eq!(ctx.memtable_mutations[0].0.sequence, 1);
725        }
726        assert_eq!(
727            ctx.wal_entry.mutations.last().unwrap().op_type,
728            OpType::Delete as i32
729        );
730    }
731
732    #[test]
733    fn test_internal_delete_respects_skip_wal_flag() {
734        check_internal_delete_respects_skip_wal_flag(false);
735        check_internal_delete_respects_skip_wal_flag(true);
736    }
737
738    fn check_internal_delete_respects_skip_wal_flag(skip_wal: bool) {
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        ctx.push_mutation(
750            OpType::Delete as i32,
751            Some(Rows {
752                schema: vec![],
753                rows: vec![api::v1::Row::default(); 2],
754            }),
755            None,
756            OptionOutputTx::none(),
757            None,
758            skip_wal,
759        );
760        assert_eq!(ctx.skip_wal(), skip_wal);
761        assert_eq!(ctx.next_sequence, 3);
762        assert_eq!(ctx.delete_num, 2);
763        assert_eq!(ctx.wal_entry.mutations.len(), usize::from(!skip_wal));
764        assert_eq!(ctx.memtable_mutations.len(), usize::from(skip_wal));
765        let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
766        let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
767        if skip_wal {
768            assert!(decoded.mutations.is_empty());
769        } else {
770            assert_eq!(decoded, ctx.wal_entry);
771        }
772    }
773
774    #[test]
775    fn test_all_request_skip_wal_keeps_entry_id_and_propagates_errors() {
776        let builder = VersionControlBuilder::new();
777        let region_id = builder.region_id();
778        let version_control = Arc::new(builder.build());
779        let mut ctx = RegionWriteCtx::new(
780            region_id,
781            &version_control,
782            Provider::raft_engine_provider(region_id.as_u64()),
783            None,
784            WriteSource::Request,
785        );
786        let (tx, rx) = oneshot::channel();
787        ctx.push_mutation(
788            OpType::Put as i32,
789            Some(Rows {
790                schema: vec![],
791                rows: vec![api::v1::Row::default(); 2],
792            }),
793            None,
794            OptionOutputTx::from(tx),
795            None,
796            true,
797        );
798        assert!(ctx.skip_wal());
799        assert!(ctx.wal_entry.mutations.is_empty());
800        assert_eq!(ctx.memtable_mutations.len(), 1);
801        assert_eq!(ctx.next_entry_id(), 1);
802        assert_eq!(ctx.next_sequence, 3);
803        ctx.set_error(Arc::new(
804            UnexpectedSnafu {
805                reason: "wal failed".to_string(),
806            }
807            .build(),
808        ));
809        drop(ctx);
810        assert!(rx.blocking_recv().unwrap().is_err());
811    }
812
813    #[test]
814    fn test_bulk_skip_wal_preserves_sequences() {
815        check_bulk_skip_wal_preserves_sequences(false);
816        check_bulk_skip_wal_preserves_sequences(true);
817    }
818
819    fn check_bulk_skip_wal_preserves_sequences(skip_wal: bool) {
820        let builder = VersionControlBuilder::new();
821        let region_id = builder.region_id();
822        let version_control = Arc::new(builder.build());
823        let mut ctx = RegionWriteCtx::new(
824            region_id,
825            &version_control,
826            Provider::raft_engine_provider(region_id.as_u64()),
827            None,
828            WriteSource::Request,
829        );
830        // Alternate policies in one context, retaining all parts for the memtable.
831        for skip in [skip_wal, false, skip_wal] {
832            assert!(ctx.push_bulk(OptionOutputTx::none(), new_bulk_part(), None, skip));
833        }
834        assert_eq!(ctx.next_sequence, 7);
835        assert_eq!(ctx.bulk_notifiers.len(), 3);
836        assert_eq!(
837            ctx.bulk_parts
838                .iter()
839                .map(|p| p.sequence)
840                .collect::<Vec<_>>(),
841            vec![1, 3, 5]
842        );
843        let encoded = crate::wal::encoder::WalEntryEncoder::new().encode_to_vec(&ctx.wal_entry);
844        let decoded = WalEntry::decode(encoded.as_slice()).unwrap();
845        assert_eq!(
846            decoded
847                .bulk_entries
848                .iter()
849                .map(|e| e.sequence)
850                .collect::<Vec<_>>(),
851            if skip_wal { vec![3] } else { vec![1, 3, 5] }
852        );
853    }
854
855    #[test]
856    fn test_set_error_marks_bulk_notifiers_failed() {
857        check_set_error_marks_bulk_notifiers_failed(false);
858        check_set_error_marks_bulk_notifiers_failed(true);
859    }
860
861    fn check_set_error_marks_bulk_notifiers_failed(skip_wal: bool) {
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::noop_provider(),
869            None,
870            WriteSource::Request,
871        );
872        let (tx, rx) = oneshot::channel();
873
874        assert!(ctx.push_bulk(OptionOutputTx::from(tx), new_bulk_part(), None, skip_wal));
875        assert_eq!(ctx.wal_entry.bulk_entries.len(), usize::from(!skip_wal));
876        assert_eq!(ctx.bulk_parts.len(), 1);
877        ctx.set_error(Arc::new(
878            UnexpectedSnafu {
879                reason: "wal failed".to_string(),
880            }
881            .build(),
882        ));
883        drop(ctx);
884
885        let result = rx.blocking_recv().unwrap();
886        assert!(result.is_err(), "bulk notifier should report WAL error");
887    }
888
889    #[tokio::test]
890    async fn test_skip_wal_writes_source_and_policy() {
891        use api::v1::helper::{tag_column_schema, time_index_column_schema};
892        use api::v1::value::ValueData;
893        use api::v1::{ColumnDataType, Row, Value};
894
895        for source in [WriteSource::Request, WriteSource::WalReplay] {
896            for skip_wal in [false, true] {
897                for bulk in [false, true] {
898                    for fail_before_install in [false, true] {
899                        let builder = VersionControlBuilder::new();
900                        let region_id = builder.region_id();
901                        let version_control = Arc::new(builder.build());
902                        let mut ctx = RegionWriteCtx::new(
903                            region_id,
904                            &version_control,
905                            Provider::raft_engine_provider(region_id.as_u64()),
906                            None,
907                            source,
908                        );
909                        let (tx, rx) = oneshot::channel();
910                        if bulk {
911                            let mut part = new_bulk_part();
912                            part.fill_missing_columns(&ctx.version.metadata).unwrap();
913                            assert!(ctx.push_bulk(OptionOutputTx::from(tx), part, None, skip_wal));
914                        } else {
915                            ctx.push_mutation(
916                                OpType::Put as i32,
917                                Some(Rows {
918                                    schema: vec![
919                                        time_index_column_schema(
920                                            "ts",
921                                            ColumnDataType::TimestampMillisecond,
922                                        ),
923                                        tag_column_schema("tag_0", ColumnDataType::String),
924                                    ],
925                                    rows: vec![Row {
926                                        values: vec![
927                                            crate::test_util::ts_ms_value(0),
928                                            Value {
929                                                value_data: Some(ValueData::StringValue(
930                                                    "a".to_string(),
931                                                )),
932                                            },
933                                        ],
934                                    }],
935                                }),
936                                None,
937                                OptionOutputTx::from(tx),
938                                None,
939                                skip_wal,
940                            );
941                        }
942                        if fail_before_install {
943                            ctx.set_error(Arc::new(
944                                UnexpectedSnafu {
945                                    reason: "WAL append failed".to_string(),
946                                }
947                                .build(),
948                            ));
949                        }
950                        ctx.write_memtables().await;
951                        assert_eq!(
952                            version_control
953                                .current()
954                                .version
955                                .memtables
956                                .has_skip_wal_writes(),
957                            source == WriteSource::Request && skip_wal && !fail_before_install,
958                            "source={source:?}, skip_wal={skip_wal}, bulk={bulk}, failed={fail_before_install}",
959                        );
960                        assert_eq!(
961                            version_control.committed_sequence(),
962                            if fail_before_install {
963                                0
964                            } else if bulk {
965                                2
966                            } else {
967                                1
968                            }
969                        );
970                        drop(ctx);
971                        assert_eq!(rx.await.unwrap().is_err(), fail_before_install);
972                    }
973                }
974            }
975        }
976    }
977
978    #[test]
979    fn test_skip_wal_writes_region_policy_and_empty_requests() {
980        for source in [WriteSource::Request, WriteSource::WalReplay] {
981            for noop in [false, true] {
982                for region_skip_wal in [false, true] {
983                    let builder = VersionControlBuilder::new();
984                    let region_id = builder.region_id();
985                    let mut version = builder.build_version();
986                    version.options.skip_wal = region_skip_wal;
987                    let version_control =
988                        Arc::new(crate::region::version::VersionControl::new(version));
989                    let provider = if noop {
990                        Provider::Noop
991                    } else {
992                        Provider::raft_engine_provider(region_id.as_u64())
993                    };
994                    let mut ctx =
995                        RegionWriteCtx::new(region_id, &version_control, provider, None, source);
996                    ctx.push_mutation(
997                        OpType::Put as i32,
998                        None,
999                        None,
1000                        OptionOutputTx::none(),
1001                        None,
1002                        true,
1003                    );
1004                    assert!(!ctx.has_skip_wal_writes);
1005                    ctx.push_mutation(
1006                        OpType::Put as i32,
1007                        Some(Rows {
1008                            schema: vec![],
1009                            rows: vec![api::v1::Row::default()],
1010                        }),
1011                        None,
1012                        OptionOutputTx::none(),
1013                        None,
1014                        false,
1015                    );
1016                    assert_eq!(
1017                        ctx.has_skip_wal_writes,
1018                        source == WriteSource::Request && (noop || region_skip_wal)
1019                    );
1020                }
1021            }
1022        }
1023    }
1024
1025    fn new_bulk_part() -> BulkPart {
1026        let schema = Arc::new(Schema::new(vec![Field::new(
1027            "ts",
1028            DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Millisecond, None),
1029            false,
1030        )]));
1031        let arrays = vec![Arc::new(TimestampMillisecondArray::from(vec![1, 2])) as ArrayRef];
1032        let batch = DfRecordBatch::try_new(schema, arrays).unwrap();
1033
1034        BulkPart {
1035            batch,
1036            max_timestamp: 2,
1037            min_timestamp: 1,
1038            sequence: 0,
1039            min_sequence: 0,
1040            timestamp_index: 0,
1041            raw_data: None,
1042        }
1043    }
1044
1045    fn sequence_rows(num_rows: usize) -> Rows {
1046        use api::v1::helper::{tag_column_schema, time_index_column_schema};
1047        Rows {
1048            schema: vec![
1049                time_index_column_schema("ts", api::v1::ColumnDataType::TimestampMillisecond),
1050                tag_column_schema("tag_0", api::v1::ColumnDataType::String),
1051            ],
1052            rows: (0..num_rows)
1053                .map(|i| api::v1::Row {
1054                    values: vec![
1055                        crate::test_util::ts_ms_value(i as i64),
1056                        api::v1::Value {
1057                            value_data: Some(api::v1::value::ValueData::StringValue(
1058                                "a".to_string(),
1059                            )),
1060                        },
1061                    ],
1062                })
1063                .collect(),
1064        }
1065    }
1066
1067    #[tokio::test]
1068    async fn test_replay_sequence_maximum_below_allocation_floor() {
1069        let builder = VersionControlBuilder::new();
1070        let region_id = builder.region_id();
1071        let vc = Arc::new(builder.build());
1072        vc.update_last_sequence(100);
1073        let provider = Provider::raft_engine_provider(region_id.as_u64());
1074        let mut ctx = RegionWriteCtx::new(
1075            region_id,
1076            &vc,
1077            provider.clone(),
1078            None,
1079            WriteSource::WalReplay,
1080        );
1081        for (sequence, num_rows) in [(40, 2), (3, 1)] {
1082            ctx.push_mutation(
1083                OpType::Put as i32,
1084                Some(sequence_rows(num_rows)),
1085                None,
1086                OptionOutputTx::none(),
1087                Some(sequence),
1088                false,
1089            );
1090        }
1091        for sequence in [10, 2] {
1092            let mut part = new_bulk_part();
1093            part.fill_missing_columns(&ctx.version.metadata).unwrap();
1094            assert!(ctx.push_bulk(OptionOutputTx::none(), part, Some(sequence), false));
1095        }
1096        assert_eq!(ctx.wal_entry.mutations[0].sequence, 40);
1097        assert_eq!(ctx.bulk_parts[0].sequence, 10);
1098        assert_eq!(ctx.bulk_parts[1].sequence, 2);
1099        assert_eq!(vc.committed_sequence(), 0);
1100        ctx.set_next_entry_id(8);
1101        ctx.write_memtables().await;
1102        assert_eq!(vc.committed_sequence(), 41);
1103        assert_eq!(vc.current().last_sequence, 100);
1104        assert_eq!(vc.current().last_entry_id, 7);
1105
1106        let mut ctx = RegionWriteCtx::new(region_id, &vc, provider, None, WriteSource::Request);
1107        ctx.push_mutation(
1108            OpType::Put as i32,
1109            Some(sequence_rows(2)),
1110            None,
1111            OptionOutputTx::none(),
1112            None,
1113            true,
1114        );
1115        assert_eq!(ctx.memtable_mutations[0].0.sequence, 101);
1116        ctx.write_memtables().await;
1117        assert_eq!(vc.committed_sequence(), 102);
1118        assert_eq!(vc.current().last_sequence, 102);
1119        assert_eq!(vc.current().last_entry_id, 7);
1120    }
1121
1122    #[tokio::test]
1123    async fn test_empty_context_does_not_publish_allocation_floor() {
1124        let builder = VersionControlBuilder::new();
1125        let region_id = builder.region_id();
1126        let vc = Arc::new(builder.build());
1127        vc.update_last_sequence(100);
1128        for empty_objects in [false, true] {
1129            let mut ctx =
1130                RegionWriteCtx::new(region_id, &vc, Provider::Noop, None, WriteSource::WalReplay);
1131            if empty_objects {
1132                ctx.push_mutation(
1133                    OpType::Put as i32,
1134                    Some(sequence_rows(0)),
1135                    None,
1136                    OptionOutputTx::none(),
1137                    Some(500),
1138                    false,
1139                );
1140                ctx.push_mutation(
1141                    OpType::Put as i32,
1142                    None,
1143                    None,
1144                    OptionOutputTx::none(),
1145                    Some(600),
1146                    false,
1147                );
1148                let mut part = new_bulk_part();
1149                part.fill_missing_columns(&ctx.version.metadata).unwrap();
1150                part.batch = part.batch.slice(0, 0);
1151                assert!(ctx.push_bulk(OptionOutputTx::none(), part, Some(700), true));
1152            }
1153            ctx.set_next_entry_id(4);
1154            ctx.write_memtables().await;
1155            let current = vc.current();
1156            assert_eq!(current.last_sequence, 100);
1157            assert_eq!(current.committed_sequence, 0);
1158            assert_eq!(current.last_entry_id, 3);
1159        }
1160    }
1161
1162    #[tokio::test]
1163    async fn test_rejected_bulk_does_not_publish_sequence() {
1164        use datatypes::arrow::array::StringDictionaryBuilder;
1165        use datatypes::arrow::datatypes::Int32Type;
1166
1167        let builder = VersionControlBuilder::new();
1168        let vc = Arc::new(builder.build());
1169        vc.update_last_sequence(100);
1170        let mut ctx = RegionWriteCtx::new(
1171            builder.region_id(),
1172            &vc,
1173            Provider::Noop,
1174            None,
1175            WriteSource::Request,
1176        );
1177        let mut part = new_bulk_part();
1178        let mut dictionary = StringDictionaryBuilder::<Int32Type>::new();
1179        dictionary.append("a").unwrap();
1180        dictionary.append("b").unwrap();
1181        let dictionary = Arc::new(dictionary.finish()) as ArrayRef;
1182        let mut fields = part.batch.schema().fields().to_vec();
1183        fields.push(Arc::new(Field::new(
1184            "tag_0",
1185            dictionary.data_type().clone(),
1186            true,
1187        )));
1188        part.batch = DfRecordBatch::try_new(
1189            Arc::new(Schema::new(fields)),
1190            vec![part.batch.column(0).clone(), dictionary],
1191        )
1192        .unwrap();
1193        let (tx, rx) = oneshot::channel();
1194        assert!(!ctx.push_bulk(OptionOutputTx::from(tx), part, Some(500), false));
1195        assert!(rx.await.unwrap().is_err());
1196        ctx.write_memtables().await;
1197        assert_eq!(vc.committed_sequence(), 0);
1198        assert_eq!(vc.current().last_sequence, 100);
1199    }
1200}