Skip to main content

operator/
write_dispatcher.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
15mod attribution;
16mod options;
17mod progress;
18#[cfg(test)]
19mod tests;
20
21use std::collections::{HashMap, HashSet, hash_map::Entry};
22use std::sync::Arc;
23
24use api::region::RegionResponse;
25use api::v1::region::{DeleteRequests, InsertRequests, RegionRequest, region_request::Body};
26use backon::{BackoffBuilder, ExponentialBuilder};
27use catalog::CatalogManagerRef;
28use common_error::ext::{BoxedError, ErrorExt, RetryHint};
29use common_meta::cache_invalidator::{CacheInvalidatorRef, Context};
30use common_meta::error::{
31    BatchRequestSnafu, Error, ExternalSnafu, Result, TableInfoNotFoundSnafu, UnexpectedSnafu,
32};
33use common_meta::instruction::CacheIdent;
34use common_meta::node_manager::NodeManagerRef;
35use common_meta::peer::{Peer, PeerRef};
36use common_meta::rpc::router;
37use futures::future;
38use partition::manager::{PartitionRuleManager, PartitionRuleManagerRef};
39use prometheus::Histogram;
40use session::write_progress::WriteProgress;
41use snafu::{IntoError, OptionExt, ResultExt, ensure};
42use store_api::storage::RegionId;
43use table::table_name::TableName;
44use tokio_util::sync::CancellationToken;
45
46pub use self::attribution::WriteAttribution;
47pub use self::options::WriteRetryOptions;
48pub use self::progress::TrackedWrite;
49use crate::error::{self, FindRegionLeaderSnafu, RequestRegionSnafu};
50use crate::req_convert::TablePartitioner;
51
52/// Dispatches already partitioned writes, retaining only failed payloads for retries.
53pub struct WriteDispatcher {
54    node_manager: NodeManagerRef,
55    partition_manager: PartitionRuleManagerRef,
56    retry: Option<WriteRetry>,
57}
58
59/// Shared reference to a [`WriteDispatcher`].
60pub type WriteDispatcherRef = Arc<WriteDispatcher>;
61
62/// The result of one write RPC attempt.
63pub(crate) enum WriteAttempt {
64    Success(RegionResponse),
65    Failed {
66        error: Error,
67        /// Retained when retries are enabled and the original client error allows retry.
68        /// The remaining retry budget is checked separately.
69        request: Option<RoutedWrite>,
70    },
71}
72
73impl WriteDispatcher {
74    /// Creates an owned-request dispatcher without retries, including for standalone.
75    pub fn new(node_manager: NodeManagerRef, partition_manager: PartitionRuleManagerRef) -> Self {
76        Self {
77            node_manager,
78            partition_manager,
79            retry: None,
80        }
81    }
82
83    /// Enables shared remote sends and retries using frontend configuration and caches.
84    pub fn with_retry(
85        mut self,
86        options: WriteRetryOptions,
87        catalog_manager: CatalogManagerRef,
88        cache_invalidator: CacheInvalidatorRef,
89    ) -> Self {
90        if options.max_retries > 0 {
91            self.retry = Some(WriteRetry {
92                node_manager: self.node_manager.clone(),
93                partition_manager: self.partition_manager.clone(),
94                options,
95                catalog_manager,
96                cache_invalidator,
97            });
98        } else {
99            self.retry = None;
100        }
101        self
102    }
103
104    /// Writes planned requests without repeating admission, default values, or Flow side effects.
105    pub async fn dispatch(&self, requests: Vec<RegionRequest>) -> Result<RegionResponse> {
106        self.dispatch_inner(requests, None).await
107    }
108
109    /// Records each datanode send attempt, including retries, in the supplied histogram.
110    pub async fn dispatch_with_metrics(
111        &self,
112        requests: Vec<RegionRequest>,
113        histogram: Histogram,
114    ) -> Result<RegionResponse> {
115        self.dispatch_inner(requests, Some(histogram)).await
116    }
117
118    /// Dispatches shared batches with row ownership retained across retries.
119    pub async fn dispatch_attributed(
120        &self,
121        requests: Vec<(RegionRequest, WriteAttribution)>,
122        histogram: Option<Histogram>,
123    ) -> Result<RegionResponse> {
124        let regions = requests
125            .iter()
126            .map(|(request, _)| write_region_id(request))
127            .collect::<Result<Vec<_>>>()?;
128        let peers = self
129            .resolve_peers(&regions)
130            .await
131            .map_err(BoxedError::new)
132            .context(ExternalSnafu)?;
133        let writes = requests
134            .into_iter()
135            .zip(regions)
136            .map(|((request, attribution), region)| {
137                Ok((get_peer(&peers, region)?, request, attribution))
138            })
139            .collect::<Result<Vec<_>>>()?;
140        let attempts = future::join_all(writes.into_iter().map(|(peer, request, attribution)| {
141            self.send_attributed(peer, request, attribution, histogram.as_ref())
142        }))
143        .await;
144        self.finish(attempts, histogram.as_ref(), None).await
145    }
146
147    /// Resolves routes before writes are detached from the caller.
148    pub(crate) async fn prepare(
149        &self,
150        requests: Vec<RegionRequest>,
151    ) -> error::Result<Vec<(PeerRef, RegionRequest)>> {
152        let requests = requests
153            .into_iter()
154            .map(|mut request| {
155                let header = request.header.take();
156                (header, split_request(request))
157            })
158            .collect::<Vec<_>>();
159        let regions = requests
160            .iter()
161            .flat_map(|(_, requests)| requests)
162            .map(write_region_id)
163            .collect::<Result<Vec<_>>>()
164            .context(RequestRegionSnafu)?;
165        let peers = self
166            .resolve_peers(&regions)
167            .await
168            .context(FindRegionLeaderSnafu)?;
169        let mut writes = Vec::new();
170        // Preserve each original envelope and peer batching when retries are disabled.
171        for (header, requests) in requests {
172            let mut by_peer: HashMap<PeerRef, RegionRequest> = HashMap::new();
173            for request in requests {
174                let peer = get_peer(
175                    &peers,
176                    write_region_id(&request).context(RequestRegionSnafu)?,
177                )
178                .context(RequestRegionSnafu)?;
179                match by_peer.entry(peer) {
180                    Entry::Vacant(entry) => {
181                        entry.insert(request);
182                    }
183                    Entry::Occupied(mut entry) => match (&mut entry.get_mut().body, request.body) {
184                        (Some(Body::Inserts(existing)), Some(Body::Inserts(next))) => {
185                            existing.requests.extend(next.requests)
186                        }
187                        (Some(Body::Deletes(existing)), Some(Body::Deletes(next))) => {
188                            existing.requests.extend(next.requests)
189                        }
190                        _ => {
191                            return UnexpectedSnafu {
192                                err_msg: "Cannot combine different write kinds",
193                            }
194                            .fail()
195                            .context(RequestRegionSnafu);
196                        }
197                    },
198                }
199            }
200            for (peer, mut request) in by_peer {
201                request.header = header.clone();
202                writes.push((peer, request));
203            }
204        }
205        Ok(writes)
206    }
207
208    /// Sends requests using routes already resolved by the caller.
209    /// Cancellation stops future retries while allowing in-flight requests to complete.
210    pub(crate) async fn execute(
211        &self,
212        writes: Vec<(PeerRef, RegionRequest)>,
213        histogram: Option<Histogram>,
214        cancellation: Option<&CancellationToken>,
215    ) -> Result<RegionResponse> {
216        self.execute_with_progress(writes, histogram, cancellation, None)
217            .await
218    }
219
220    pub(crate) async fn execute_with_progress(
221        &self,
222        writes: Vec<(PeerRef, RegionRequest)>,
223        histogram: Option<Histogram>,
224        cancellation: Option<&CancellationToken>,
225        progress: Option<Arc<WriteProgress>>,
226    ) -> Result<RegionResponse> {
227        let histogram = histogram.as_ref();
228        let progress = progress.as_ref();
229        let attempts = future::join_all(writes.into_iter().map(|(peer, request)| async move {
230            if self.retry.is_some() || progress.is_some() {
231                self.send_batch(peer, request, Vec::new(), histogram, progress)
232                    .await
233            } else {
234                vec![(Vec::new(), self.send(peer, request, histogram).await)]
235            }
236        }))
237        .await
238        .into_iter()
239        .flatten()
240        .map(|(_, attempt)| attempt)
241        .collect();
242        self.finish(attempts, histogram, cancellation).await
243    }
244
245    /// Sends the first attempt immediately so bulk encoding can overlap other sends.
246    pub(crate) async fn send(
247        &self,
248        peer: PeerRef,
249        request: RegionRequest,
250        histogram: Option<&Histogram>,
251    ) -> WriteAttempt {
252        self.send_attributed(peer, request, WriteAttribution::default(), histogram)
253            .await
254    }
255
256    async fn send_attributed(
257        &self,
258        peer: PeerRef,
259        mut request: RegionRequest,
260        attribution: WriteAttribution,
261        histogram: Option<&Histogram>,
262    ) -> WriteAttempt {
263        if let Some(retry) = &self.retry {
264            if matches!(&request.body, Some(Body::Inserts(_) | Body::Deletes(_))) {
265                api::region::set_accept_partial_result(request.header.get_or_insert_default());
266            }
267            return retry
268                .send(
269                    RoutedWrite {
270                        peer,
271                        request: Arc::new(request),
272                        attribution,
273                        owners: None,
274                    },
275                    histogram,
276                )
277                .await;
278        }
279        let _timer = histogram.map(Histogram::start_timer);
280        let datanode = self.node_manager.datanode(&peer).await;
281        match datanode.handle(request).await {
282            Ok(response) => acknowledged(response, &attribution),
283            Err(error) => WriteAttempt::Failed {
284                error,
285                request: None,
286            },
287        }
288    }
289
290    /// Collects first attempts and retries their failures together, refreshing routes once per round.
291    pub(crate) async fn finish(
292        &self,
293        attempts: Vec<WriteAttempt>,
294        histogram: Option<&Histogram>,
295        cancellation: Option<&CancellationToken>,
296    ) -> Result<RegionResponse> {
297        if let Some(retry) = &self.retry {
298            if retry.should_retry(&attempts, 0) {
299                return retry.retry(attempts, histogram, cancellation).await;
300            }
301            let mut response = RegionResponse::new(0);
302            let failures = merge_attempts(attempts, &mut response)?;
303            return match failures.into_iter().next() {
304                Some((_, error)) => Err(error),
305                None => Ok(response),
306            };
307        }
308        let mut response = RegionResponse::new(0);
309        for attempt in attempts {
310            match attempt {
311                WriteAttempt::Success(result) => merge_response(&mut response, result),
312                WriteAttempt::Failed { error, .. } => return Err(error),
313            }
314        }
315        Ok(response)
316    }
317
318    /// Resolves only the requested peers, loading each table route once.
319    pub(crate) async fn resolve_peers(
320        &self,
321        regions: &[RegionId],
322    ) -> partition::error::Result<HashMap<RegionId, PeerRef>> {
323        resolve_peers(&self.partition_manager, regions).await
324    }
325
326    /// `owners` has one vector per request entry for tracked sends, or is empty otherwise.
327    async fn send_batch(
328        &self,
329        peer: PeerRef,
330        mut request: RegionRequest,
331        owners: Vec<Vec<usize>>,
332        histogram: Option<&Histogram>,
333        progress: Option<&Arc<WriteProgress>>,
334    ) -> Vec<(Vec<usize>, WriteAttempt)> {
335        if !matches!(&request.body, Some(Body::Inserts(_) | Body::Deletes(_))) {
336            return vec![(
337                owners.into_iter().flatten().collect(),
338                self.send(peer, request, histogram).await,
339            )];
340        }
341        // Upgrade datanodes first and roll back frontends first: old datanodes
342        // cannot report which entries succeeded, so retrying a batch may replay writes.
343        api::region::set_accept_partial_result(request.header.get_or_insert_default());
344        if self.retry.is_none() {
345            // Standalone consumes owned requests; retaining an Arc prevents local dispatch.
346            let requester = self.node_manager.datanode(&peer).await;
347            let _timer = histogram.map(Histogram::start_timer);
348            return match requester.handle(request).await {
349                Ok(response) => {
350                    if let Some(progress) = progress {
351                        progress.add_written(response.affected_rows);
352                    }
353                    vec![WriteAttempt::Success(response)]
354                }
355                Err(Error::PartialRegion { response }) => {
356                    if let Some(progress) = progress {
357                        progress.add_written(response.affected_rows);
358                    }
359                    let mut attempts = vec![WriteAttempt::Success(RegionResponse {
360                        affected_rows: response.affected_rows,
361                        extensions: response.extensions,
362                        metadata: Vec::new(),
363                    })];
364                    attempts.extend(response.failures.into_iter().map(|(_, error)| {
365                        WriteAttempt::Failed {
366                            error,
367                            request: None,
368                        }
369                    }));
370                    attempts
371                }
372                Err(error) => vec![WriteAttempt::Failed {
373                    error,
374                    request: None,
375                }],
376            }
377            .into_iter()
378            .map(|attempt| (Vec::new(), attempt))
379            .collect();
380        }
381        let request = Arc::new(request);
382        let requester = self.node_manager.datanode(&peer).await;
383        let result = {
384            let _timer = histogram.map(Histogram::start_timer);
385            requester.handle(Arc::clone(&request)).await
386        };
387        let error = match result {
388            Ok(response) => {
389                if let Some(progress) = progress {
390                    progress.add_written(response.affected_rows);
391                }
392                return vec![(
393                    owners.into_iter().flatten().collect(),
394                    WriteAttempt::Success(response),
395                )];
396            }
397            Err(error) => error,
398        };
399        let request = match Arc::try_unwrap(request) {
400            Ok(request) => request,
401            Err(_) => {
402                return vec![(
403                    owners.into_iter().flatten().collect(),
404                    WriteAttempt::Failed {
405                        error: UnexpectedSnafu {
406                            err_msg: "Region request is still shared after its RPC completed",
407                        }
408                        .build(),
409                        request: None,
410                    },
411                )];
412            }
413        };
414        let requests = split_request(request);
415        let mut owners = owners.into_iter();
416        match error {
417            Error::PartialRegion { response } => {
418                if let Some(progress) = progress {
419                    progress.add_written(response.affected_rows);
420                }
421                let mut failures: HashMap<_, _> = response.failures.into_iter().collect();
422                let mut attempts = Vec::new();
423                let mut success_owners = Vec::new();
424                for (index, request) in requests.into_iter().enumerate() {
425                    let owners = owners.next();
426                    if let Some(error) = failures.remove(&index) {
427                        let failed_owners = owners.clone().unwrap_or_default();
428                        let request = error.retry_hint().is_retryable().then(|| RoutedWrite {
429                            peer: Arc::clone(&peer),
430                            attribution: direct_attribution(&request, progress.cloned()),
431                            request: Arc::new(request),
432                            owners,
433                        });
434                        attempts.push((failed_owners, WriteAttempt::Failed { error, request }));
435                    } else {
436                        success_owners.extend(owners.unwrap_or_default());
437                    }
438                }
439                attempts.insert(
440                    0,
441                    (
442                        success_owners,
443                        WriteAttempt::Success(RegionResponse {
444                            affected_rows: response.affected_rows,
445                            extensions: response.extensions,
446                            metadata: Vec::new(),
447                        }),
448                    ),
449                );
450                attempts
451            }
452            error => {
453                // A whole-RPC failure cannot confirm success for any entry.
454                let error = Arc::new(error);
455                requests
456                    .into_iter()
457                    .map(|request| {
458                        let owners = owners.next();
459                        let failed_owners = owners.clone().unwrap_or_default();
460                        let request = error.retry_hint().is_retryable().then(|| RoutedWrite {
461                            peer: Arc::clone(&peer),
462                            attribution: direct_attribution(&request, progress.cloned()),
463                            request: Arc::new(request),
464                            owners,
465                        });
466                        (
467                            failed_owners,
468                            WriteAttempt::Failed {
469                                error: BatchRequestSnafu.into_error(Arc::clone(&error)),
470                                request,
471                            },
472                        )
473                    })
474                    .collect()
475            }
476        }
477    }
478
479    async fn dispatch_inner(
480        &self,
481        requests: Vec<RegionRequest>,
482        histogram: Option<Histogram>,
483    ) -> Result<RegionResponse> {
484        let writes = self
485            .prepare(requests)
486            .await
487            .map_err(BoxedError::new)
488            .context(ExternalSnafu)?;
489        self.execute(writes, histogram, None).await
490    }
491}
492
493/// Sends shared requests and coordinates retries with refreshed routes.
494struct WriteRetry {
495    node_manager: NodeManagerRef,
496    partition_manager: PartitionRuleManagerRef,
497    options: WriteRetryOptions,
498    catalog_manager: CatalogManagerRef,
499    cache_invalidator: CacheInvalidatorRef,
500}
501
502/// A single-region write request paired with its destination peer.
503pub(crate) struct RoutedWrite {
504    peer: PeerRef,
505    request: Arc<RegionRequest>,
506    attribution: WriteAttribution,
507    owners: Option<Vec<usize>>,
508}
509
510impl WriteRetry {
511    async fn send(&self, write: RoutedWrite, histogram: Option<&Histogram>) -> WriteAttempt {
512        let _timer = histogram.map(Histogram::start_timer);
513        let requester = self.node_manager.datanode(&write.peer).await;
514        match requester.handle(Arc::clone(&write.request)).await {
515            Ok(response) => acknowledged(response, &write.attribution),
516            Err(error) => {
517                let request = error.retry_hint().is_retryable().then_some(write);
518                WriteAttempt::Failed { error, request }
519            }
520        }
521    }
522
523    fn should_retry(&self, attempts: &[WriteAttempt], retries: usize) -> bool {
524        if retries >= self.options.max_retries {
525            return false;
526        }
527        let mut retryable = false;
528        for attempt in attempts {
529            match attempt {
530                WriteAttempt::Success(_) => {}
531                WriteAttempt::Failed {
532                    request: Some(_), ..
533                } => retryable = true,
534                WriteAttempt::Failed { request: None, .. } => return false,
535            }
536        }
537        retryable
538    }
539
540    async fn retry(
541        &self,
542        mut attempts: Vec<WriteAttempt>,
543        histogram: Option<&Histogram>,
544        cancellation: Option<&CancellationToken>,
545    ) -> Result<RegionResponse> {
546        let mut response = RegionResponse::new(0);
547        let mut backoff = ExponentialBuilder::default()
548            .with_min_delay(self.options.initial_backoff)
549            .with_max_delay(self.options.max_backoff)
550            .with_factor(self.options.backoff_multiplier)
551            .with_max_times(self.options.max_retries)
552            .with_jitter()
553            .build();
554        let mut retries = 0;
555        while self.should_retry(&attempts, retries) {
556            let Some(delay) = backoff.next() else {
557                break;
558            };
559            if let Some(cancellation) = cancellation {
560                tokio::select! {
561                    biased;
562                    _ = cancellation.cancelled() => break,
563                    _ = tokio::time::sleep(delay) => {}
564                }
565            } else {
566                tokio::time::sleep(delay).await;
567            }
568            if cancellation.is_some_and(CancellationToken::is_cancelled) {
569                break;
570            }
571            let failures = merge_attempts(attempts, &mut response)?;
572            let mut first_error = None;
573            let mut refresh = Vec::new();
574            let mut pending = Vec::new();
575            for (write, error) in failures {
576                if error.retry_hint() == RetryHint::RetryAfterRouteRefresh {
577                    refresh.push(write);
578                } else {
579                    pending.push(write);
580                }
581                first_error.get_or_insert(error);
582            }
583            if !refresh.is_empty() {
584                pending.extend(self.reroute_failed_writes(refresh).await?);
585            }
586            if let Some(error) =
587                first_error.filter(|_| cancellation.is_some_and(CancellationToken::is_cancelled))
588            {
589                return Err(error);
590            }
591            attempts =
592                future::join_all(pending.into_iter().map(|write| self.send(write, histogram)))
593                    .await;
594            retries += 1;
595        }
596        match merge_attempts(attempts, &mut response)?.into_iter().next() {
597            Some((_, error)) => Err(error),
598            None => Ok(response),
599        }
600    }
601
602    async fn reroute_failed_writes(&self, writes: Vec<RoutedWrite>) -> Result<Vec<RoutedWrite>> {
603        let regions = writes
604            .iter()
605            .map(|write| write_region_id(&write.request))
606            .collect::<Result<Vec<_>>>()?;
607        self.invalidate_routes(&regions).await?;
608        let mut table_names = HashMap::new();
609        for table_id in regions
610            .iter()
611            .map(|region| region.table_id())
612            .collect::<HashSet<_>>()
613        {
614            // Raw metadata is only used to recover the name of the failed request's table.
615            let table_info = self
616                .catalog_manager
617                .table_info_by_id(table_id)
618                .await
619                .map_err(BoxedError::new)
620                .context(ExternalSnafu)?
621                .with_context(|| TableInfoNotFoundSnafu {
622                    table: table_id.to_string(),
623                })?;
624            table_names.insert(
625                table_id,
626                TableName::new(
627                    &table_info.catalog_name,
628                    &table_info.schema_name,
629                    &table_info.name,
630                ),
631            );
632        }
633        // TableId invalidation refreshes metadata and routes, but TableCache is keyed by name.
634        // Use the refreshed names in case a table was renamed while the write was pending.
635        let caches = table_names
636            .values()
637            .cloned()
638            .map(CacheIdent::TableName)
639            .collect::<Vec<_>>();
640        self.cache_invalidator
641            .invalidate(&Context::default(), &caches)
642            .await?;
643        let mut partitioners = HashMap::new();
644        for (table_id, name) in table_names {
645            let table = self
646                .catalog_manager
647                .table(
648                    &name.catalog_name,
649                    &name.schema_name,
650                    &name.table_name,
651                    None,
652                )
653                .await
654                .map_err(BoxedError::new)
655                .context(ExternalSnafu)?
656                .with_context(|| TableInfoNotFoundSnafu {
657                    table: name.to_string(),
658                })?;
659            let table_info = table.table_info();
660            ensure!(
661                table_info.table_id() == table_id,
662                UnexpectedSnafu {
663                    err_msg: format!(
664                        "Table {name} changed ID from {table_id} to {} while retrying writes",
665                        table_info.table_id()
666                    ),
667                }
668            );
669            let partitioner = TablePartitioner::try_new(&self.partition_manager, &table_info)
670                .await
671                .map_err(BoxedError::new)
672                .context(ExternalSnafu)?;
673            partitioners.insert(table_id, (table_info, partitioner));
674        }
675        let mut rebuilt_requests = Vec::new();
676        for write in writes {
677            let request = Arc::try_unwrap(write.request).map_err(|_| {
678                UnexpectedSnafu {
679                    err_msg: "Write request is still shared after its RPC completed",
680                }
681                .build()
682            })?;
683            let table_id = write_region_id(&request)?.table_id();
684            let (table_info, partitioner) =
685                partitioners.get(&table_id).context(UnexpectedSnafu {
686                    err_msg: "Refreshed write has no partition snapshot",
687                })?;
688            if let Some(owners) = write.owners {
689                let requests = partitioner
690                    .rebuild_tracked_write_requests(request, table_info, Some(owners))
691                    .map_err(BoxedError::new)
692                    .context(ExternalSnafu)?;
693                rebuilt_requests.extend(
694                    requests
695                        .into_iter()
696                        .map(|(request, owners)| ((request, WriteAttribution::default()), owners)),
697                );
698            } else {
699                let requests = partitioner
700                    .rebuild_attributed_write(request, table_info, write.attribution)
701                    .map_err(BoxedError::new)
702                    .context(ExternalSnafu)?;
703                rebuilt_requests.extend(requests.into_iter().map(|request| (request, None)));
704            }
705        }
706        let (requests, owners): (Vec<_>, Vec<_>) = rebuilt_requests.into_iter().unzip();
707        let mut writes = self.prepare(requests).await?;
708        for (write, owners) in writes.iter_mut().zip(owners) {
709            write.owners = owners;
710        }
711        Ok(writes)
712    }
713
714    async fn prepare(
715        &self,
716        requests: Vec<(RegionRequest, WriteAttribution)>,
717    ) -> Result<Vec<RoutedWrite>> {
718        let regions = requests
719            .iter()
720            .map(|(request, _)| write_region_id(request))
721            .collect::<Result<Vec<_>>>()?;
722        let peers = resolve_peers(&self.partition_manager, &regions)
723            .await
724            .map_err(BoxedError::new)
725            .context(ExternalSnafu)?;
726        requests
727            .into_iter()
728            .zip(regions)
729            .map(|((request, attribution), region)| {
730                Ok(RoutedWrite {
731                    peer: get_peer(&peers, region)?,
732                    request: Arc::new(request),
733                    attribution,
734                    owners: None,
735                })
736            })
737            .collect()
738    }
739
740    async fn invalidate_routes(&self, regions: &[RegionId]) -> Result<()> {
741        let mut table_ids = regions
742            .iter()
743            .map(|region| region.table_id())
744            .collect::<HashSet<_>>();
745        let mut physical_ids = HashSet::new();
746        for table_id in &table_ids {
747            let (physical_id, _) = self
748                .partition_manager
749                .find_physical_table_route_with_id(*table_id)
750                .await
751                .map_err(BoxedError::new)
752                .context(ExternalSnafu)?;
753            physical_ids.insert(physical_id);
754        }
755        table_ids.extend(physical_ids);
756        let caches = table_ids
757            .into_iter()
758            .map(CacheIdent::TableId)
759            .collect::<Vec<_>>();
760        self.cache_invalidator
761            .invalidate(&Context::default(), &caches)
762            .await
763    }
764}
765
766fn acknowledged(response: RegionResponse, attribution: &WriteAttribution) -> WriteAttempt {
767    match attribution.record(response.affected_rows) {
768        Ok(()) => WriteAttempt::Success(response),
769        Err(error) => WriteAttempt::Failed {
770            error,
771            request: None,
772        },
773    }
774}
775
776pub(crate) fn write_row_count(request: &RegionRequest) -> usize {
777    match &request.body {
778        Some(Body::Inserts(inserts)) => inserts
779            .requests
780            .iter()
781            .filter_map(|request| request.rows.as_ref())
782            .map(|rows| rows.rows.len())
783            .sum(),
784        Some(Body::Deletes(deletes)) => deletes
785            .requests
786            .iter()
787            .filter_map(|request| request.rows.as_ref())
788            .map(|rows| rows.rows.len())
789            .sum(),
790        _ => 0,
791    }
792}
793
794fn direct_attribution(
795    request: &RegionRequest,
796    progress: Option<Arc<WriteProgress>>,
797) -> WriteAttribution {
798    WriteAttribution::new(write_row_count(request), progress)
799}
800
801async fn resolve_peers(
802    partition_manager: &PartitionRuleManager,
803    regions: &[RegionId],
804) -> partition::error::Result<HashMap<RegionId, PeerRef>> {
805    let table_ids = regions
806        .iter()
807        .map(|region| region.table_id())
808        .collect::<HashSet<_>>();
809    let mut physical_tables = HashMap::new();
810    let mut routes = HashMap::new();
811    for table_id in table_ids {
812        let (physical_id, route) = partition_manager
813            .find_physical_table_route_with_id(table_id)
814            .await?;
815        physical_tables.insert(table_id, physical_id);
816        routes.entry(physical_id).or_insert(route);
817    }
818    let mut shared_peers: HashMap<&Peer, PeerRef> = HashMap::new();
819    let mut peers = HashMap::with_capacity(regions.len());
820    for region in regions {
821        if peers.contains_key(region) {
822            continue;
823        }
824        let peer = physical_tables
825            .get(&region.table_id())
826            .and_then(|physical_id| routes.get(physical_id))
827            .and_then(|route| {
828                router::find_region_leader_ref(&route.region_routes, region.region_number())
829            })
830            .context(partition::error::FindLeaderSnafu {
831                region_id: *region,
832                table_id: region.table_id(),
833            })?;
834        let peer = shared_peers
835            .entry(peer)
836            .or_insert_with(|| Arc::new(peer.clone()));
837        peers.insert(*region, Arc::clone(peer));
838    }
839    Ok(peers)
840}
841
842fn get_peer(peers: &HashMap<RegionId, PeerRef>, region: RegionId) -> Result<PeerRef> {
843    peers.get(&region).cloned().context(UnexpectedSnafu {
844        err_msg: "Planned write has no resolved peer",
845    })
846}
847
848fn merge_attempts(
849    attempts: Vec<WriteAttempt>,
850    response: &mut RegionResponse,
851) -> Result<Vec<(RoutedWrite, Error)>> {
852    let mut failures = Vec::new();
853    let mut terminal_error = None;
854    for attempt in attempts {
855        match attempt {
856            WriteAttempt::Success(result) => merge_response(response, result),
857            WriteAttempt::Failed {
858                error,
859                request: Some(write),
860            } => failures.push((write, error)),
861            WriteAttempt::Failed {
862                error,
863                request: None,
864            } => {
865                terminal_error.get_or_insert(error);
866            }
867        }
868    }
869    // A terminal error takes precedence over retained retryable failures.
870    match terminal_error {
871        Some(error) => Err(error),
872        None => Ok(failures),
873    }
874}
875
876fn merge_response(response: &mut RegionResponse, result: RegionResponse) {
877    response.affected_rows += result.affected_rows;
878    response.extensions.extend(result.extensions);
879    response.metadata = result.metadata;
880}
881
882fn split_request(request: RegionRequest) -> Vec<RegionRequest> {
883    let RegionRequest { header, body } = request;
884    let bodies: Vec<Body> = match body {
885        Some(Body::Inserts(inserts)) => inserts
886            .requests
887            .into_iter()
888            .map(|request| {
889                Body::Inserts(InsertRequests {
890                    requests: vec![request],
891                })
892            })
893            .collect(),
894        Some(Body::Deletes(deletes)) => deletes
895            .requests
896            .into_iter()
897            .map(|request| {
898                Body::Deletes(DeleteRequests {
899                    requests: vec![request],
900                })
901            })
902            .collect(),
903        body => return vec![RegionRequest { header, body }],
904    };
905    bodies
906        .into_iter()
907        .map(|body| RegionRequest {
908            header: header.clone(),
909            body: Some(body),
910        })
911        .collect()
912}
913
914fn write_region_id(request: &RegionRequest) -> Result<RegionId> {
915    let region_id = match &request.body {
916        Some(Body::Inserts(inserts)) => match inserts.requests.as_slice() {
917            [request] => Some(request.region_id),
918            _ => None,
919        },
920        Some(Body::Deletes(deletes)) => match deletes.requests.as_slice() {
921            [request] => Some(request.region_id),
922            _ => None,
923        },
924        Some(Body::BulkInsert(bulk)) => Some(bulk.region_id),
925        _ => None,
926    };
927    region_id.map(RegionId::from_u64).ok_or_else(|| {
928        UnexpectedSnafu {
929            err_msg: "Expected a single-region write request",
930        }
931        .build()
932    })
933}