1use std::sync::Arc;
16use std::time::Instant;
17
18use common_telemetry::{info, warn};
19use snafu::ensure;
20use store_api::logstore::LogStore;
21
22use crate::error::{self, Result};
23use crate::region::MitoRegion;
24use crate::region::opener::{check_wal_history, replay_memtable};
25use crate::wal::Wal;
26use crate::wal::entry_distributor::WalEntryReceiver;
27
28pub struct RegionCatchupTask<S> {
29 entry_receiver: Option<WalEntryReceiver>,
30 region: Arc<MitoRegion>,
31 replay_checkpoint_entry_id: Option<u64>,
32 expected_last_entry_id: Option<u64>,
33 allow_stale_entries: bool,
34 wal: Wal<S>,
35}
36
37impl<S: LogStore> RegionCatchupTask<S> {
38 pub fn new(region: Arc<MitoRegion>, wal: Wal<S>, allow_stale_entries: bool) -> Self {
39 Self {
40 entry_receiver: None,
41 region,
42 replay_checkpoint_entry_id: None,
43 expected_last_entry_id: None,
44 allow_stale_entries,
45 wal,
46 }
47 }
48
49 pub(crate) fn with_expected_last_entry_id(
51 mut self,
52 expected_last_entry_id: Option<u64>,
53 ) -> Self {
54 self.expected_last_entry_id = expected_last_entry_id;
55 self
56 }
57
58 pub(crate) fn with_entry_receiver(mut self, entry_receiver: Option<WalEntryReceiver>) -> Self {
60 self.entry_receiver = entry_receiver;
61 self
62 }
63
64 pub(crate) fn with_replay_checkpoint_entry_id(
66 mut self,
67 replay_checkpoint_entry_id: Option<u64>,
68 ) -> Self {
69 self.replay_checkpoint_entry_id = replay_checkpoint_entry_id;
70 self
71 }
72
73 pub async fn run(&mut self) -> Result<()> {
74 if self.region.provider.is_remote_wal() {
75 self.remote_wal_catchup().await
76 } else {
77 self.local_wal_catchup().await
78 }
79 }
80
81 async fn remote_wal_catchup(&mut self) -> Result<()> {
82 let flushed_entry_id = self.region.version_control.current().last_entry_id;
83 let replay_from_entry_id = self
84 .replay_checkpoint_entry_id
85 .unwrap_or(flushed_entry_id)
86 .max(flushed_entry_id);
87 let region_id = self.region.region_id;
88 info!(
89 "Trying to replay memtable for region: {region_id}, provider: {:?}, replay from entry id: {replay_from_entry_id}, flushed entry id: {flushed_entry_id}",
90 self.region.provider
91 );
92 check_wal_history(
93 &self.wal,
94 &self.region.provider,
95 &self.region.version_control.current().version,
96 )
97 .await?;
98 let timer = Instant::now();
99 let wal_entry_reader = self
100 .entry_receiver
101 .take()
102 .map(|r| Box::new(r) as _)
103 .unwrap_or_else(|| self.wal.wal_entry_reader(&self.region.provider, region_id));
104 let on_region_opened = self.wal.on_region_opened();
105 let last_entry_id = replay_memtable(
106 &self.region.provider,
107 wal_entry_reader,
108 region_id,
109 replay_from_entry_id,
110 &self.region.version_control,
111 self.allow_stale_entries,
112 on_region_opened,
113 )
114 .await?;
115 info!(
116 "Elapsed: {:?}, region: {region_id}, provider: {:?} catchup finished. replay from entry id: {replay_from_entry_id}, flushed entry id: {flushed_entry_id}, last entry id: {last_entry_id}, expected: {:?}.",
117 timer.elapsed(),
118 self.region.provider,
119 self.expected_last_entry_id
120 );
121 if let Some(expected_last_entry_id) = self.expected_last_entry_id {
122 ensure!(
123 last_entry_id >= expected_last_entry_id,
125 error::UnexpectedSnafu {
126 reason: format!(
127 "Failed to catchup region {}, it was expected to replay to {}, but actually replayed to {}",
128 region_id, expected_last_entry_id, last_entry_id,
129 ),
130 }
131 )
132 }
133 Ok(())
134 }
135
136 async fn local_wal_catchup(&mut self) -> Result<()> {
137 let version = self.region.version_control.current();
138 let mut flushed_entry_id = version.last_entry_id;
139 let region_id = self.region.region_id;
140 let latest_entry_id = self
141 .wal
142 .store()
143 .latest_entry_id(&self.region.provider)
144 .unwrap_or_default();
145 info!(
146 "Skips to replay memtable for region: {}, flushed entry id: {}, latest entry id: {}",
147 region_id, flushed_entry_id, latest_entry_id
148 );
149
150 if latest_entry_id > flushed_entry_id {
151 warn!(
152 "Found latest entry id is greater than flushed entry id, using latest entry id as flushed entry id, region: {}, latest entry id: {}, flushed entry id: {}",
153 region_id, latest_entry_id, flushed_entry_id
154 );
155 flushed_entry_id = latest_entry_id;
156 self.region.version_control.set_entry_id(flushed_entry_id);
157 }
158 let on_region_opened = self.wal.on_region_opened();
159 on_region_opened(region_id, flushed_entry_id, &self.region.provider).await?;
160 Ok(())
161 }
162}