Skip to main content

mito2/worker/
handle_write.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
15//! Handling write requests.
16
17use std::collections::{HashMap, HashSet, hash_map};
18use std::sync::Arc;
19
20use api::v1::OpType;
21use common_telemetry::{debug, error};
22use snafu::ensure;
23use store_api::codec::PrimaryKeyEncoding;
24use store_api::logstore::LogStore;
25use store_api::storage::RegionId;
26
27use crate::error::{
28    InvalidRequestSnafu, PartitionExprVersionMismatchSnafu, RegionLeaseExpiredSnafu,
29    RegionNotFoundSnafu, RegionStateSnafu, RejectWriteInStagingSnafu, RejectWriteSnafu, Result,
30    UnacknowledgedWriteSnafu,
31};
32use crate::metrics;
33use crate::metrics::{
34    WRITE_REJECT_TOTAL, WRITE_ROWS_TOTAL, WRITE_STAGE_ELAPSED, WRITE_STALL_TOTAL,
35};
36use crate::region::{RegionLeaderState, RegionRoleState};
37use crate::region_write_ctx::{RegionWriteCtx, WriteSource};
38use crate::request::{
39    SenderBulkRequest, SenderWriteRequest, WriteRequest, check_bulk_insert_batch,
40};
41use crate::wal::Wal;
42use crate::worker::RegionWorkerLoop;
43
44impl<S: LogStore> RegionWorkerLoop<S> {
45    /// Takes and handles all write requests.
46    pub(crate) async fn handle_write_requests(
47        &mut self,
48        write_requests: &mut Vec<SenderWriteRequest>,
49        bulk_requests: &mut Vec<SenderBulkRequest>,
50        allow_stall: bool,
51    ) {
52        if write_requests.is_empty() && bulk_requests.is_empty() {
53            return;
54        }
55
56        let write_region_ids = write_region_ids(write_requests, bulk_requests);
57
58        // Check region pressure before writes to match the global write buffer behavior.
59        self.maybe_flush_worker();
60        let pressure = self.maybe_flush_write_regions(write_region_ids);
61
62        if self.should_reject_write() {
63            // The memory pressure is still too high, reject write requests.
64            reject_write_requests(write_requests, bulk_requests);
65            // Also reject all stalled requests.
66            self.reject_stalled_requests();
67            return;
68        }
69
70        if !pressure.rejected_region_ids.is_empty() {
71            reject_region_write_requests(
72                &pressure.rejected_region_ids,
73                write_requests,
74                bulk_requests,
75            );
76            for region_id in &pressure.rejected_region_ids {
77                self.reject_region_stalled_requests(region_id);
78            }
79            if write_requests.is_empty() && bulk_requests.is_empty() {
80                return;
81            }
82        }
83
84        if self.write_buffer_manager.should_stall() && allow_stall {
85            let stalled_count = (write_requests.len() + bulk_requests.len()) as i64;
86            self.stalling_count.add(stalled_count);
87            WRITE_STALL_TOTAL.inc_by(stalled_count as u64);
88            self.stalled_requests.append(write_requests, bulk_requests);
89            self.listener.on_write_stall();
90            return;
91        }
92
93        if allow_stall {
94            self.stall_region_write_requests(
95                &pressure.stalled_region_ids,
96                write_requests,
97                bulk_requests,
98            );
99            if write_requests.is_empty() && bulk_requests.is_empty() {
100                return;
101            }
102        }
103
104        // Prepare write context.
105        let mut region_ctxs = {
106            let _timer = WRITE_STAGE_ELAPSED
107                .with_label_values(&["prepare_ctx"])
108                .start_timer();
109            self.prepare_region_write_ctx(write_requests, bulk_requests)
110        };
111
112        // Write to WAL.
113        {
114            let _timer = WRITE_STAGE_ELAPSED
115                .with_label_values(&["write_wal"])
116                .start_timer();
117            if !write_wal(&self.wal, &mut region_ctxs).await {
118                // Failed to write to the WAL, all waiters are notified with the error.
119                return;
120            }
121        }
122        self.listener.on_write_wal_end().await;
123        self.fail_writes_after_lease_expiry(&mut region_ctxs);
124
125        let (mut put_rows, mut delete_rows) = (0, 0);
126        // Write to memtables.
127        {
128            let _timer = WRITE_STAGE_ELAPSED
129                .with_label_values(&["write_memtable"])
130                .start_timer();
131            if region_ctxs.len() == 1 {
132                // fast path for single region.
133                let mut region_ctx = region_ctxs.into_values().next().unwrap();
134                region_ctx.write_memtables().await;
135                put_rows += region_ctx.put_num;
136                delete_rows += region_ctx.delete_num;
137            } else {
138                let region_write_task = region_ctxs
139                    .into_values()
140                    .map(|mut region_ctx| {
141                        // use tokio runtime to schedule tasks.
142                        common_runtime::spawn_global(async move {
143                            region_ctx.write_memtables().await;
144                            (region_ctx.put_num, region_ctx.delete_num)
145                        })
146                    })
147                    .collect::<Vec<_>>();
148
149                for result in futures::future::join_all(region_write_task).await {
150                    match result {
151                        Ok((put, delete)) => {
152                            put_rows += put;
153                            delete_rows += delete;
154                        }
155                        Err(e) => {
156                            error!(e; "unexpected error when joining region write tasks");
157                        }
158                    }
159                }
160            }
161        }
162        WRITE_ROWS_TOTAL
163            .with_label_values(&["put"])
164            .inc_by(put_rows as u64);
165        WRITE_ROWS_TOTAL
166            .with_label_values(&["delete"])
167            .inc_by(delete_rows as u64);
168    }
169
170    /// Handles stalled write requests whose regions no longer need to stall.
171    pub(crate) async fn handle_stalled_requests(&mut self) {
172        let region_ids = self
173            .stalled_requests
174            .requests
175            .keys()
176            .copied()
177            .collect::<HashSet<_>>();
178        let pressure = self.maybe_flush_write_regions(region_ids);
179        for region_id in &pressure.rejected_region_ids {
180            self.reject_region_stalled_requests(region_id);
181        }
182        let ready_region_ids = self
183            .stalled_requests
184            .requests
185            .keys()
186            .filter(|region_id| !pressure.stalled_region_ids.contains(region_id))
187            .copied()
188            .collect::<Vec<_>>();
189
190        // These requests have already been stalled. Retry ready regions without stalling the
191        // same requests again. Regions that still exceed their limit remain in the queue until
192        // their own flush releases the pressure.
193        for region_id in ready_region_ids {
194            self.handle_region_stalled_requests(&region_id, false).await;
195        }
196    }
197
198    /// Rejects all stalled requests.
199    pub(crate) fn reject_stalled_requests(&mut self) {
200        let stalled = std::mem::take(&mut self.stalled_requests);
201        self.stalling_count.sub(stalled.stalled_count() as i64);
202        for (_, (_, mut requests, mut bulk)) in stalled.requests {
203            reject_write_requests(&mut requests, &mut bulk);
204        }
205    }
206
207    /// Rejects a specific region's stalled requests.
208    pub(crate) fn reject_region_stalled_requests(&mut self, region_id: &RegionId) {
209        debug!("Rejects stalled requests for region {}", region_id);
210        let (mut requests, mut bulk) = self.stalled_requests.remove(region_id);
211        self.stalling_count
212            .sub((requests.len() + bulk.len()) as i64);
213        reject_write_requests(&mut requests, &mut bulk);
214    }
215
216    /// Fails a specific region's stalled requests if the region no longer exists.
217    pub(crate) fn fail_region_stalled_requests_as_not_found(&mut self, region_id: &RegionId) {
218        debug!(
219            "Fails stalled requests for region {} as region not found",
220            region_id
221        );
222        let (requests, bulk) = self.stalled_requests.remove(region_id);
223        self.stalling_count
224            .sub((requests.len() + bulk.len()) as i64);
225
226        for req in requests {
227            req.sender.send(
228                RegionNotFoundSnafu {
229                    region_id: req.request.region_id,
230                }
231                .fail(),
232            );
233        }
234        for req in bulk {
235            req.sender.send(
236                RegionNotFoundSnafu {
237                    region_id: req.region_id,
238                }
239                .fail(),
240            );
241        }
242    }
243
244    /// Handles a specific region's stalled requests.
245    ///
246    /// `allow_stall` should be false for backpressure retry paths to avoid stalling the same
247    /// requests again. It should remain true for non-backpressure retries, such as requests stalled
248    /// by alter, staging, and region editing. Global reject backpressure still applies before the
249    /// stall check.
250    pub(crate) async fn handle_region_stalled_requests(
251        &mut self,
252        region_id: &RegionId,
253        allow_stall: bool,
254    ) {
255        debug!("Handles stalled requests for region {}", region_id);
256        let (mut requests, mut bulk) = self.stalled_requests.remove(region_id);
257        self.stalling_count
258            .sub((requests.len() + bulk.len()) as i64);
259        self.handle_write_requests(&mut requests, &mut bulk, allow_stall)
260            .await;
261    }
262
263    /// Processes same-batch writes for a region before handling its edit-completion notification.
264    ///
265    /// The worker dispatch loop handles background notifications before the current batch's write
266    /// buffer. Without this step, writes that arrived during edit N could be classified only after
267    /// edit N+1 is started, placing them behind that next edit.
268    pub(crate) async fn handle_buffered_region_write_requests(
269        &mut self,
270        region_id: &RegionId,
271        write_requests: &mut Vec<SenderWriteRequest>,
272        bulk_requests: &mut Vec<SenderBulkRequest>,
273    ) {
274        let mut current_region_write_requests = write_requests
275            .extract_if(.., |r| r.request.region_id == *region_id)
276            .collect::<Vec<_>>();
277
278        let mut current_region_bulk_requests = bulk_requests
279            .extract_if(.., |r| r.region_id == *region_id)
280            .collect::<Vec<_>>();
281
282        self.handle_write_requests(
283            &mut current_region_write_requests,
284            &mut current_region_bulk_requests,
285            true,
286        )
287        .await;
288    }
289}
290
291impl<S> RegionWorkerLoop<S> {
292    /// Validates and groups requests by region.
293    fn prepare_region_write_ctx(
294        &mut self,
295        write_requests: &mut Vec<SenderWriteRequest>,
296        bulk_requests: &mut Vec<SenderBulkRequest>,
297    ) -> HashMap<RegionId, RegionWriteCtx> {
298        // Initialize region write context map.
299        let mut region_ctxs = HashMap::new();
300        // One reading serves the batch, as preparing it does not wait.
301        let lease_now = self.node_lease.is_enabled().then(|| self.node_lease.now());
302        self.process_write_requests(&mut region_ctxs, write_requests, lease_now);
303        self.process_bulk_requests(&mut region_ctxs, bulk_requests, lease_now);
304        region_ctxs
305    }
306
307    fn process_write_requests(
308        &mut self,
309        region_ctxs: &mut HashMap<RegionId, RegionWriteCtx>,
310        write_requests: &mut Vec<SenderWriteRequest>,
311        lease_now: Option<u64>,
312    ) {
313        for mut sender_req in write_requests.drain(..) {
314            let region_id = sender_req.request.region_id;
315
316            // If region is waiting for alteration, add requests to pending writes.
317            if self.flush_scheduler.has_pending_ddls(region_id) {
318                // TODO(yingwen): consider adding some metrics for this.
319                // Safety: The region has pending ddls.
320                self.flush_scheduler
321                    .add_write_request_to_pending(sender_req);
322                continue;
323            }
324
325            // Checks whether the region exists and is it stalling.
326            if let hash_map::Entry::Vacant(e) = region_ctxs.entry(region_id) {
327                let Some(region) = self
328                    .regions
329                    .get_region_or(region_id, &mut sender_req.sender)
330                else {
331                    // No such region.
332                    continue;
333                };
334                #[cfg(test)]
335                debug!(
336                    "Handling write request for region {}, state: {:?}",
337                    region_id,
338                    region.state()
339                );
340                match region.state() {
341                    RegionRoleState::Leader(RegionLeaderState::Writable)
342                    | RegionRoleState::Leader(RegionLeaderState::Staging) => {
343                        let leader_deadline = region.leader_deadline.get();
344                        if lease_now.is_some_and(|now| now >= leader_deadline) {
345                            sender_req
346                                .sender
347                                .send(RegionLeaseExpiredSnafu { region_id }.fail());
348                            continue;
349                        }
350                        if region.reject_all_writes_in_staging() {
351                            sender_req
352                                .sender
353                                .send(RejectWriteInStagingSnafu { region_id }.fail());
354                            continue;
355                        }
356
357                        let mut region_ctx = RegionWriteCtx::new(
358                            region.region_id,
359                            &region.version_control,
360                            region.provider.clone(),
361                            Some(region.region_stats.written_bytes.clone()),
362                            WriteSource::Request,
363                        );
364                        region_ctx.lease = lease_now.map(|_| (region, leader_deadline));
365
366                        e.insert(region_ctx);
367                    }
368                    RegionRoleState::Leader(RegionLeaderState::Altering)
369                    | RegionRoleState::Leader(RegionLeaderState::Editing) => {
370                        // Editing is transient: queue the write so edit completion can drain it
371                        // before starting the next queued edit.
372                        debug!(
373                            "Region {} is {:?}, add request to pending writes",
374                            region.region_id,
375                            region.state()
376                        );
377                        self.stalling_count.add(1);
378                        WRITE_STALL_TOTAL.inc();
379                        self.stalled_requests.push(sender_req);
380                        continue;
381                    }
382                    RegionRoleState::Leader(RegionLeaderState::EnteringStaging) => {
383                        debug!(
384                            "Region {} is entering staging, add request to pending writes",
385                            region.region_id
386                        );
387                        self.stalling_count.add(1);
388                        WRITE_STALL_TOTAL.inc();
389                        self.stalled_requests.push(sender_req);
390                        continue;
391                    }
392                    state => {
393                        // The region is not writable.
394                        sender_req.sender.send(
395                            RegionStateSnafu {
396                                region_id,
397                                state,
398                                expect: RegionRoleState::Leader(RegionLeaderState::Writable),
399                            }
400                            .fail(),
401                        );
402                        continue;
403                    }
404                }
405            }
406
407            // Safety: Now we ensure the region exists.
408            let region_ctx = region_ctxs.get_mut(&region_id).unwrap();
409            let Some(region) = self
410                .regions
411                .get_region_or(region_id, &mut sender_req.sender)
412            else {
413                continue;
414            };
415            if region.reject_all_writes_in_staging() {
416                sender_req
417                    .sender
418                    .send(RejectWriteInStagingSnafu { region_id }.fail());
419                continue;
420            }
421            let expected_version = region.expected_partition_expr_version();
422            if let Err(e) = check_partition_expr_version(
423                region_id,
424                expected_version,
425                sender_req.request.partition_expr_version,
426            ) {
427                sender_req.sender.send(Err(e));
428                continue;
429            }
430
431            if let Err(e) = check_op_type(
432                region_ctx.version().options.append_mode,
433                &sender_req.request,
434            ) {
435                // Do not allow non-put op under append mode.
436                sender_req.sender.send(Err(e));
437
438                continue;
439            }
440
441            // Double check the request schema
442            let need_fill_missing_columns =
443                if let Some(ref region_metadata) = sender_req.request.region_metadata {
444                    region_ctx.version().metadata.schema_version != region_metadata.schema_version
445                } else {
446                    true
447                };
448            // Only fill missing columns if primary key is dense encoded.
449            if need_fill_missing_columns
450                && sender_req.request.primary_key_encoding() == PrimaryKeyEncoding::Dense
451                && let Err(e) = sender_req
452                    .request
453                    .maybe_fill_missing_columns(&region_ctx.version().metadata)
454            {
455                sender_req.sender.send(Err(e));
456
457                continue;
458            }
459
460            // Collect requests by region.
461            region_ctx.push_mutation(
462                sender_req.request.op_type as i32,
463                Some(sender_req.request.rows),
464                sender_req.request.hint,
465                sender_req.sender,
466                None,
467                sender_req.request.skip_wal,
468            );
469        }
470    }
471
472    /// Processes bulk insert requests.
473    fn process_bulk_requests(
474        &mut self,
475        region_ctxs: &mut HashMap<RegionId, RegionWriteCtx>,
476        requests: &mut Vec<SenderBulkRequest>,
477        lease_now: Option<u64>,
478    ) {
479        let _timer = metrics::REGION_WORKER_HANDLE_WRITE_ELAPSED
480            .with_label_values(&["prepare_bulk_request"])
481            .start_timer();
482        for mut bulk_req in requests.drain(..) {
483            let region_id = bulk_req.region_id;
484            // If region is waiting for alteration, add requests to pending writes.
485            if self.flush_scheduler.has_pending_ddls(region_id) {
486                // Safety: The region has pending ddls.
487                self.flush_scheduler.add_bulk_request_to_pending(bulk_req);
488                continue;
489            }
490
491            // Checks whether the region exists and is it stalling.
492            if let hash_map::Entry::Vacant(e) = region_ctxs.entry(region_id) {
493                let Some(region) = self.regions.get_region_or(region_id, &mut bulk_req.sender)
494                else {
495                    continue;
496                };
497                match region.state() {
498                    RegionRoleState::Leader(RegionLeaderState::Writable)
499                    | RegionRoleState::Leader(RegionLeaderState::Staging) => {
500                        let leader_deadline = region.leader_deadline.get();
501                        if lease_now.is_some_and(|now| now >= leader_deadline) {
502                            bulk_req
503                                .sender
504                                .send(RegionLeaseExpiredSnafu { region_id }.fail());
505                            continue;
506                        }
507                        if region.reject_all_writes_in_staging() {
508                            bulk_req
509                                .sender
510                                .send(RejectWriteInStagingSnafu { region_id }.fail());
511                            continue;
512                        }
513                        let mut region_ctx = RegionWriteCtx::new(
514                            region.region_id,
515                            &region.version_control,
516                            region.provider.clone(),
517                            Some(region.region_stats.written_bytes.clone()),
518                            WriteSource::Request,
519                        );
520                        region_ctx.lease = lease_now.map(|_| (region, leader_deadline));
521
522                        e.insert(region_ctx);
523                    }
524                    RegionRoleState::Leader(RegionLeaderState::Altering)
525                    | RegionRoleState::Leader(RegionLeaderState::Editing) => {
526                        // Editing is transient: queue the bulk write so edit completion can drain
527                        // it before starting the next queued edit.
528                        debug!(
529                            "Region {} is {:?}, add request to pending writes",
530                            region.region_id,
531                            region.state()
532                        );
533                        self.stalling_count.add(1);
534                        WRITE_STALL_TOTAL.inc();
535                        self.stalled_requests.push_bulk(bulk_req);
536                        continue;
537                    }
538                    state => {
539                        // The region is not writable.
540                        bulk_req.sender.send(
541                            RegionStateSnafu {
542                                region_id,
543                                state,
544                                expect: RegionRoleState::Leader(RegionLeaderState::Writable),
545                            }
546                            .fail(),
547                        );
548                        continue;
549                    }
550                }
551            }
552
553            // Safety: Now we ensure the region exists.
554            let region_ctx = region_ctxs.get_mut(&region_id).unwrap();
555            let Some(region) = self.regions.get_region_or(region_id, &mut bulk_req.sender) else {
556                continue;
557            };
558            if region.reject_all_writes_in_staging() {
559                bulk_req
560                    .sender
561                    .send(RejectWriteInStagingSnafu { region_id }.fail());
562                continue;
563            }
564            let expected_version = region.expected_partition_expr_version();
565            if let Err(e) = check_partition_expr_version(
566                region_id,
567                expected_version,
568                bulk_req.partition_expr_version,
569            ) {
570                bulk_req.sender.send(Err(e));
571                continue;
572            }
573
574            // Double-check the request schema
575            let need_fill_missing_columns =
576                !bulk_req.region_metadata.is_some_and(|aligned_schema| {
577                    aligned_schema.schema_version == region_ctx.version().metadata.schema_version
578                });
579
580            // Fill missing columns if needed. The batch is checked again as the region
581            // schema may have changed since the request was checked.
582            if need_fill_missing_columns {
583                let metadata = &region_ctx.version().metadata;
584                if let Err(e) = check_bulk_insert_batch(metadata, &bulk_req.request.batch)
585                    .and_then(|_| bulk_req.request.fill_missing_columns(metadata))
586                {
587                    bulk_req.sender.send(Err(e));
588                    continue;
589                }
590            }
591
592            // Collect requests by region.
593            if !region_ctx.push_bulk(bulk_req.sender, bulk_req.request, None, bulk_req.skip_wal) {
594                continue;
595            }
596        }
597    }
598
599    /// Fails the waiters of writes whose WAL append completed at or after their region's Leader
600    /// deadline. The writes still go to the memtables, as their entries are in the WAL.
601    fn fail_writes_after_lease_expiry(&self, region_ctxs: &mut HashMap<RegionId, RegionWriteCtx>) {
602        if !self.node_lease.is_enabled() {
603            return;
604        }
605        let now = self.node_lease.now();
606        for (region_id, region_ctx) in region_ctxs.iter_mut() {
607            // A Leader grant may have extended the deadline during the append.
608            let expired = region_ctx.lease.as_ref().is_some_and(|(region, deadline)| {
609                now >= *deadline && now >= region.leader_deadline.get()
610            });
611            if !expired || region_ctx.is_failed() {
612                continue;
613            }
614            region_ctx.fail_waiters(Arc::new(
615                UnacknowledgedWriteSnafu {
616                    region_id: *region_id,
617                }
618                .build(),
619            ));
620        }
621    }
622
623    /// Returns true if the engine needs to reject some write requests.
624    pub(crate) fn should_reject_write(&self) -> bool {
625        // If memory usage reaches high threshold (we should also consider stalled requests) returns true.
626        self.write_buffer_manager.memory_usage() + self.stalled_requests.estimated_size
627            >= self.config.global_write_buffer_reject_size.as_bytes() as usize
628    }
629
630    fn stall_region_write_requests(
631        &mut self,
632        stalled_region_ids: &HashSet<RegionId>,
633        write_requests: &mut Vec<SenderWriteRequest>,
634        bulk_requests: &mut Vec<SenderBulkRequest>,
635    ) {
636        let mut stalled_count = 0;
637        let mut stalled_write_requests = write_requests
638            .extract_if(.., |req| {
639                stalled_region_ids.contains(&req.request.region_id)
640            })
641            .collect::<Vec<_>>();
642        let mut stalled_bulk_requests = bulk_requests
643            .extract_if(.., |req| stalled_region_ids.contains(&req.region_id))
644            .collect::<Vec<_>>();
645
646        stalled_count += stalled_write_requests.len() + stalled_bulk_requests.len();
647        self.stalled_requests
648            .append(&mut stalled_write_requests, &mut stalled_bulk_requests);
649
650        if stalled_count > 0 {
651            let stalled_count = stalled_count as i64;
652            self.stalling_count.add(stalled_count);
653            WRITE_STALL_TOTAL.inc_by(stalled_count as u64);
654            self.listener.on_write_stall();
655        }
656    }
657}
658
659/// Writes WAL entries of all region contexts to the WAL in one batch and updates
660/// the next entry id of each region on success.
661///
662/// Returns `false` if the batch fails to be written to the WAL. In this case all
663/// contexts are consumed and their waiters are notified with the error, so the
664/// caller should skip the memtable phase.
665async fn write_wal<S: LogStore>(
666    wal: &Wal<S>,
667    region_ctxs: &mut HashMap<RegionId, RegionWriteCtx>,
668) -> bool {
669    let mut wal_writer = wal.writer();
670    let mut has_wal_entries = false;
671    for region_ctx in region_ctxs.values_mut() {
672        if region_ctx.skip_wal() {
673            continue;
674        }
675        if let Err(e) = region_ctx.add_wal_entry(&mut wal_writer).map_err(Arc::new) {
676            region_ctx.set_error(e);
677        } else {
678            has_wal_entries = true;
679        }
680    }
681    // All-skipped batches should not touch the log store, even with an empty append.
682    if !has_wal_entries {
683        return true;
684    }
685    match wal_writer.write_to_wal().await.map_err(Arc::new) {
686        Ok(response) => {
687            for (region_id, region_ctx) in region_ctxs.iter_mut() {
688                if region_ctx.skip_wal() {
689                    continue;
690                }
691                // The entry of a failed region (e.g. failed to build its WAL entry) is
692                // not in the batch so the response has no last entry id for it. Its
693                // waiters are already notified with the error.
694                if region_ctx.is_failed() {
695                    continue;
696                }
697
698                // Safety: the log store implementation ensures that either the `write_to_wal` fails and no
699                // response is returned or the last entry ids for each region in the batch do exist.
700                let last_entry_id = response.last_entry_ids.get(region_id).unwrap();
701                region_ctx.set_next_entry_id(last_entry_id + 1);
702            }
703            true
704        }
705        Err(e) => {
706            // Failed to write wal.
707            for (_, mut region_ctx) in region_ctxs.drain() {
708                region_ctx.set_error(e.clone());
709            }
710            false
711        }
712    }
713}
714
715/// Send rejected error to all `write_requests`.
716fn reject_write_requests(
717    write_requests: &mut Vec<SenderWriteRequest>,
718    bulk_requests: &mut Vec<SenderBulkRequest>,
719) {
720    WRITE_REJECT_TOTAL.inc_by(write_requests.len() as u64);
721
722    for req in write_requests.drain(..) {
723        req.sender.send(
724            RejectWriteSnafu {
725                region_id: req.request.region_id,
726            }
727            .fail(),
728        );
729    }
730    for req in bulk_requests.drain(..) {
731        let region_id = req.region_id;
732        req.sender.send(RejectWriteSnafu { region_id }.fail());
733    }
734}
735
736fn reject_region_write_requests(
737    rejected_region_ids: &HashSet<RegionId>,
738    write_requests: &mut Vec<SenderWriteRequest>,
739    bulk_requests: &mut Vec<SenderBulkRequest>,
740) {
741    let mut rejected_write_requests = write_requests
742        .extract_if(.., |req| {
743            rejected_region_ids.contains(&req.request.region_id)
744        })
745        .collect::<Vec<_>>();
746    let mut rejected_bulk_requests = bulk_requests
747        .extract_if(.., |req| rejected_region_ids.contains(&req.region_id))
748        .collect::<Vec<_>>();
749    reject_write_requests(&mut rejected_write_requests, &mut rejected_bulk_requests);
750}
751
752fn write_region_ids(
753    write_requests: &[SenderWriteRequest],
754    bulk_requests: &[SenderBulkRequest],
755) -> HashSet<RegionId> {
756    write_requests
757        .iter()
758        .map(|req| req.request.region_id)
759        .chain(bulk_requests.iter().map(|req| req.region_id))
760        .collect()
761}
762
763/// Rejects delete request under append mode.
764fn check_op_type(append_mode: bool, request: &WriteRequest) -> Result<()> {
765    if append_mode {
766        ensure!(
767            request.op_type == OpType::Put,
768            InvalidRequestSnafu {
769                region_id: request.region_id,
770                reason: "DELETE is not allowed under append mode",
771            }
772        );
773    }
774
775    Ok(())
776}
777
778fn check_partition_expr_version(
779    region_id: RegionId,
780    expected_version: u64,
781    request_version: Option<u64>,
782) -> Result<()> {
783    let request_version = match request_version {
784        None => return Ok(()),
785        Some(value) => value,
786    };
787    if request_version != expected_version {
788        return PartitionExprVersionMismatchSnafu {
789            region_id,
790            request_version,
791            expected_version,
792        }
793        .fail();
794    }
795    Ok(())
796}
797
798#[cfg(test)]
799mod tests {
800    use api::v1::helper::{tag_column_schema, time_index_column_schema};
801    use api::v1::value::ValueData;
802    use api::v1::{ColumnDataType, Row, Rows};
803    use common_recordbatch::DfRecordBatch;
804    use datatypes::arrow::array::{ArrayRef, StringArray, TimestampMillisecondArray};
805    use datatypes::arrow::datatypes::{DataType, Field, Schema};
806    use futures::stream;
807    use log_store::error::{
808        Error as LogStoreError, IllegalStateSnafu, InvalidProviderSnafu, Result as LogStoreResult,
809    };
810    use store_api::logstore::entry::{Entry, NaiveEntry};
811    use store_api::logstore::provider::Provider;
812    use store_api::logstore::{AppendBatchResponse, EntryId, SendableEntryStream};
813    use store_api::region_request::AffectedRows;
814    use tokio::sync::oneshot;
815
816    use super::*;
817    use crate::memtable::bulk::part::BulkPart;
818    use crate::request::OptionOutputTx;
819    use crate::test_util::ts_ms_value;
820    use crate::test_util::version_util::VersionControlBuilder;
821
822    fn new_bulk_part(num_rows: i64) -> BulkPart {
823        let schema = Arc::new(Schema::new(vec![
824            Field::new("tag_0", DataType::Utf8, true),
825            Field::new(
826                "ts",
827                DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Millisecond, None),
828                false,
829            ),
830        ]));
831        let tag = Arc::new(StringArray::from_iter_values(
832            (0..num_rows).map(|value| value.to_string()),
833        )) as ArrayRef;
834        let ts = Arc::new(TimestampMillisecondArray::from(
835            (0..num_rows).collect::<Vec<_>>(),
836        )) as ArrayRef;
837        let batch = DfRecordBatch::try_new(schema, vec![tag, ts]).unwrap();
838
839        BulkPart {
840            batch,
841            max_timestamp: num_rows - 1,
842            min_timestamp: 0,
843            sequence: 0,
844            min_sequence: 0,
845            timestamp_index: 1,
846            raw_data: None,
847        }
848    }
849
850    /// A log store that fails to build entries for `failing_region` and fails the
851    /// whole batch when `fail_append` is true.
852    #[derive(Debug, Default)]
853    struct MockLogStore {
854        failing_region: Option<RegionId>,
855        fail_append: bool,
856    }
857
858    #[async_trait::async_trait]
859    impl LogStore for MockLogStore {
860        type Error = LogStoreError;
861
862        async fn stop(&self) -> LogStoreResult<()> {
863            Ok(())
864        }
865
866        async fn append_batch(&self, entries: Vec<Entry>) -> LogStoreResult<AppendBatchResponse> {
867            if self.fail_append {
868                return IllegalStateSnafu {}.fail();
869            }
870            let mut last_entry_ids = HashMap::new();
871            for entry in &entries {
872                let last_entry_id = last_entry_ids.entry(entry.region_id()).or_insert(0);
873                *last_entry_id = entry.entry_id().max(*last_entry_id);
874            }
875            Ok(AppendBatchResponse { last_entry_ids })
876        }
877
878        async fn read(
879            &self,
880            _provider: &Provider,
881            _entry_id: EntryId,
882        ) -> LogStoreResult<SendableEntryStream<'static, Entry, Self::Error>> {
883            Ok(Box::pin(stream::empty()))
884        }
885
886        async fn create_namespace(&self, _ns: &Provider) -> LogStoreResult<()> {
887            Ok(())
888        }
889
890        async fn delete_namespace(&self, _ns: &Provider) -> LogStoreResult<()> {
891            Ok(())
892        }
893
894        async fn list_namespaces(&self) -> LogStoreResult<Vec<Provider>> {
895            Ok(vec![])
896        }
897
898        async fn obsolete(
899            &self,
900            _provider: &Provider,
901            _region_id: RegionId,
902            _entry_id: EntryId,
903        ) -> LogStoreResult<()> {
904            Ok(())
905        }
906
907        async fn obsolete_all(
908            &self,
909            _provider: &Provider,
910            _region_id: RegionId,
911        ) -> LogStoreResult<()> {
912            Ok(())
913        }
914
915        fn entry(
916            &self,
917            data: Vec<u8>,
918            entry_id: EntryId,
919            region_id: RegionId,
920            provider: &Provider,
921        ) -> LogStoreResult<Entry> {
922            if self.failing_region == Some(region_id) {
923                return InvalidProviderSnafu {
924                    expected: "raft_engine",
925                    actual: "mock",
926                }
927                .fail();
928            }
929            Ok(Entry::Naive(NaiveEntry {
930                provider: provider.clone(),
931                region_id,
932                entry_id,
933                data,
934            }))
935        }
936
937        fn latest_entry_id(&self, _provider: &Provider) -> LogStoreResult<EntryId> {
938            Ok(0)
939        }
940    }
941
942    fn new_region_ctx(
943        region_id: RegionId,
944        skip_wal: bool,
945    ) -> (RegionWriteCtx, oneshot::Receiver<Result<AffectedRows>>) {
946        let version_control = Arc::new(VersionControlBuilder::new().build());
947        let mut ctx = RegionWriteCtx::new(
948            region_id,
949            &version_control,
950            Provider::raft_engine_provider(region_id.as_u64()),
951            None,
952            WriteSource::Request,
953        );
954        let (tx, rx) = oneshot::channel();
955        ctx.push_mutation(
956            OpType::Put as i32,
957            Some(Rows {
958                schema: vec![
959                    time_index_column_schema("ts", ColumnDataType::TimestampMillisecond),
960                    tag_column_schema("tag_0", ColumnDataType::String),
961                ],
962                rows: vec![Row {
963                    values: vec![
964                        ts_ms_value(0),
965                        api::v1::Value {
966                            value_data: Some(ValueData::StringValue("a".to_string())),
967                        },
968                    ],
969                }],
970            }),
971            None,
972            OptionOutputTx::from(tx),
973            None,
974            skip_wal,
975        );
976        (ctx, rx)
977    }
978
979    #[tokio::test]
980    async fn test_request_skip_wal_does_not_append_empty_batch() {
981        // Only change the request flag. A failing log store demonstrates that
982        // the all-skipped path never invokes append_batch, including empty appends.
983        check_request_skip_wal_does_not_append_empty_batch(false, false).await;
984        check_request_skip_wal_does_not_append_empty_batch(false, true).await;
985        check_request_skip_wal_does_not_append_empty_batch(true, false).await;
986        check_request_skip_wal_does_not_append_empty_batch(true, true).await;
987    }
988
989    async fn check_request_skip_wal_does_not_append_empty_batch(skip_wal: bool, bulk: bool) {
990        let region_id = RegionId::new(1, 1);
991        let wal = Wal::new(Arc::new(MockLogStore {
992            fail_append: true,
993            ..Default::default()
994        }));
995        let (ctx, rx) = if bulk {
996            let version_control = Arc::new(VersionControlBuilder::new().build());
997            let mut ctx = RegionWriteCtx::new(
998                region_id,
999                &version_control,
1000                Provider::raft_engine_provider(region_id.as_u64()),
1001                None,
1002                WriteSource::Request,
1003            );
1004            let (tx, rx) = oneshot::channel();
1005            assert!(ctx.push_bulk(OptionOutputTx::from(tx), new_bulk_part(1), None, skip_wal));
1006            (ctx, rx)
1007        } else {
1008            new_region_ctx(region_id, skip_wal)
1009        };
1010        let version_control = ctx.version_control().clone();
1011        let mut contexts = HashMap::from([(region_id, ctx)]);
1012        assert_eq!(write_wal(&wal, &mut contexts).await, skip_wal);
1013        if skip_wal {
1014            let ctx = contexts.get_mut(&region_id).unwrap();
1015            assert_eq!(ctx.next_entry_id(), 1);
1016            ctx.write_memtables().await;
1017            assert_eq!(version_control.committed_sequence(), 1);
1018            assert_eq!(version_control.current().last_entry_id, 0);
1019        }
1020        drop(contexts);
1021        assert_eq!(rx.await.unwrap().is_ok(), skip_wal);
1022    }
1023
1024    #[tokio::test]
1025    async fn test_write_wal_skips_region_failed_to_build_entry() {
1026        let failing_region = RegionId::new(1, 1);
1027        let ok_region = RegionId::new(1, 2);
1028        let wal = Wal::new(Arc::new(MockLogStore {
1029            failing_region: Some(failing_region),
1030            ..Default::default()
1031        }));
1032
1033        let mut region_ctxs = HashMap::new();
1034        let (ctx, failing_rx) = new_region_ctx(failing_region, false);
1035        let failing_committed_sequence = ctx.version_control().committed_sequence();
1036        region_ctxs.insert(failing_region, ctx);
1037        let (ctx, ok_rx) = new_region_ctx(ok_region, false);
1038        let ok_committed_sequence = ctx.version_control().committed_sequence();
1039        region_ctxs.insert(ok_region, ctx);
1040        let entry_id = region_ctxs[&ok_region].next_entry_id();
1041
1042        // The failed region must not fail the batch.
1043        assert!(write_wal(&wal, &mut region_ctxs).await);
1044
1045        assert!(region_ctxs[&failing_region].is_failed());
1046        assert!(!region_ctxs[&ok_region].is_failed());
1047        assert_eq!(entry_id + 1, region_ctxs[&ok_region].next_entry_id());
1048
1049        for region_ctx in region_ctxs.values_mut() {
1050            region_ctx.write_memtables().await;
1051        }
1052
1053        assert_eq!(
1054            failing_committed_sequence,
1055            region_ctxs[&failing_region]
1056                .version_control()
1057                .committed_sequence()
1058        );
1059        assert_eq!(
1060            ok_committed_sequence + 1,
1061            region_ctxs[&ok_region]
1062                .version_control()
1063                .committed_sequence()
1064        );
1065
1066        drop(region_ctxs);
1067        assert!(failing_rx.await.unwrap().is_err());
1068        assert_eq!(1, ok_rx.await.unwrap().unwrap());
1069    }
1070
1071    #[tokio::test]
1072    async fn test_bulk_write_sequence_not_committed_before_install_worker_level() {
1073        check_bulk_write_sequence_not_committed_before_install_worker_level(false).await;
1074        check_bulk_write_sequence_not_committed_before_install_worker_level(true).await;
1075    }
1076
1077    async fn check_bulk_write_sequence_not_committed_before_install_worker_level(skip_wal: bool) {
1078        let region_id = RegionId::new(1, 1);
1079        let version_control = Arc::new(VersionControlBuilder::new().build());
1080
1081        let mut region_ctxs = HashMap::new();
1082        let mut ctx = RegionWriteCtx::new(
1083            region_id,
1084            &version_control,
1085            Provider::raft_engine_provider(region_id.as_u64()),
1086            None,
1087            WriteSource::Request,
1088        );
1089        let (tx, rx) = oneshot::channel();
1090        assert!(ctx.push_bulk(OptionOutputTx::from(tx), new_bulk_part(3), None, skip_wal));
1091        region_ctxs.insert(region_id, ctx);
1092
1093        let wal = Wal::new(Arc::new(MockLogStore::default()));
1094        assert!(write_wal(&wal, &mut region_ctxs).await);
1095        assert!(!region_ctxs[&region_id].is_failed());
1096
1097        let mut barrier = crate::region_write_ctx::test_hooks::arm_bulk_install_barrier(
1098            region_id,
1099            version_control.clone(),
1100        );
1101
1102        let write_handle = tokio::spawn(async move {
1103            let mut region_ctx = region_ctxs.remove(&region_id).unwrap();
1104            region_ctx.write_memtables().await;
1105        });
1106
1107        tokio::time::timeout(
1108            std::time::Duration::from_secs(10),
1109            barrier.wait_until_reached(),
1110        )
1111        .await
1112        .expect("bulk write never reached the install barrier");
1113
1114        assert_eq!(
1115            0,
1116            version_control.committed_sequence(),
1117            "committed sequence leaked before the bulk part was installed"
1118        );
1119
1120        barrier.release();
1121        write_handle.await.expect("bulk write should complete");
1122        assert_eq!(
1123            3,
1124            version_control.committed_sequence(),
1125            "committed sequence must cover the installed bulk rows"
1126        );
1127
1128        assert_eq!(3, rx.await.unwrap().unwrap());
1129    }
1130
1131    #[tokio::test]
1132    async fn test_write_wal_all_regions_failed_to_build_entries() {
1133        let failing_region = RegionId::new(1, 1);
1134        let wal = Wal::new(Arc::new(MockLogStore {
1135            failing_region: Some(failing_region),
1136            ..Default::default()
1137        }));
1138
1139        let mut region_ctxs = HashMap::new();
1140        let (ctx, rx) = new_region_ctx(failing_region, false);
1141        region_ctxs.insert(failing_region, ctx);
1142
1143        // Writing an empty batch to the WAL succeeds, the failed region must not panic
1144        // the worker.
1145        assert!(write_wal(&wal, &mut region_ctxs).await);
1146
1147        assert!(region_ctxs[&failing_region].is_failed());
1148        drop(region_ctxs);
1149        assert!(rx.await.unwrap().is_err());
1150    }
1151
1152    #[tokio::test]
1153    async fn test_write_wal_append_batch_failure() {
1154        let region_id = RegionId::new(1, 1);
1155        let wal = Wal::new(Arc::new(MockLogStore {
1156            fail_append: true,
1157            ..Default::default()
1158        }));
1159
1160        let mut region_ctxs = HashMap::new();
1161        let (ctx, rx) = new_region_ctx(region_id, false);
1162        region_ctxs.insert(region_id, ctx);
1163
1164        assert!(!write_wal(&wal, &mut region_ctxs).await);
1165
1166        // All contexts are consumed and waiters are notified with the error.
1167        assert!(region_ctxs.is_empty());
1168        assert!(rx.await.unwrap().is_err());
1169    }
1170}