mito2/manifest/
checkpointer.rs1use std::fmt::Debug;
16use std::sync::Arc;
17use std::sync::atomic::{AtomicU64, Ordering};
18
19use common_runtime::JoinHandle;
20use common_telemetry::{error, info, warn};
21use store_api::storage::RegionId;
22use store_api::{MIN_VERSION, ManifestVersion};
23#[cfg(test)]
24use tokio::sync::Notify;
25
26use crate::error::Result;
27use crate::manifest::action::{RegionCheckpoint, RegionManifest};
28use crate::manifest::manager::RegionManifestOptions;
29use crate::manifest::storage::ManifestObjectStore;
30use crate::metrics::MANIFEST_OP_ELAPSED;
31
32#[derive(Debug)]
34pub(crate) struct Checkpointer {
35 manifest_options: RegionManifestOptions,
36 inner: Arc<Inner>,
37 checkpoint_task: Option<JoinHandle<()>>,
38 #[cfg(test)]
39 pending_checkpoint_wait_started: Arc<Notify>,
40}
41
42#[derive(Debug)]
43struct Inner {
44 region_id: RegionId,
45 manifest_store: ManifestObjectStore,
46 last_checkpoint_version: AtomicU64,
47}
48
49impl Inner {
50 async fn do_checkpoint(&self, checkpoint: RegionCheckpoint) {
51 let _t = MANIFEST_OP_ELAPSED
52 .with_label_values(&["checkpoint"])
53 .start_timer();
54
55 let region_id = self.region_id();
56 let version = checkpoint.last_version();
57 let checkpoint = match checkpoint.encode() {
58 Ok(checkpoint) => checkpoint,
59 Err(e) => {
60 error!(e; "Failed to encode checkpoint {:?}", checkpoint);
61 return;
62 }
63 };
64 if let Err(e) = self
65 .manifest_store
66 .save_checkpoint(version, &checkpoint)
67 .await
68 {
69 error!(e; "Failed to save checkpoint for region {}", region_id);
70 return;
71 }
72
73 self.last_checkpoint_version
79 .store(version, Ordering::Relaxed);
80
81 if let Err(e) = self.manifest_store.delete_until(version, true).await {
82 warn!(e; "Failed to delete manifest actions until version {} for region {}, leftover files will be ignored on recovery", version, region_id);
83 }
84
85 info!(
86 "Checkpoint for region {} success, version: {}",
87 region_id, version
88 );
89 }
90
91 fn region_id(&self) -> RegionId {
92 self.region_id
93 }
94}
95
96impl Checkpointer {
97 pub(crate) fn new(
98 region_id: RegionId,
99 manifest_options: RegionManifestOptions,
100 manifest_store: ManifestObjectStore,
101 last_checkpoint_version: ManifestVersion,
102 ) -> Self {
103 Self {
104 manifest_options,
105 inner: Arc::new(Inner {
106 region_id,
107 manifest_store,
108 last_checkpoint_version: AtomicU64::new(last_checkpoint_version),
109 }),
110 checkpoint_task: None,
111 #[cfg(test)]
112 pending_checkpoint_wait_started: Arc::new(Notify::new()),
113 }
114 }
115
116 pub(crate) fn last_checkpoint_version(&self) -> ManifestVersion {
117 self.inner.last_checkpoint_version.load(Ordering::Relaxed)
118 }
119
120 pub(crate) fn update_manifest_removed_files(
123 &self,
124 mut manifest: RegionManifest,
125 ) -> Result<RegionManifest> {
126 let opt = &self.manifest_options.remove_file_options;
127
128 manifest.removed_files.evict_old_removed_files(opt)?;
129
130 Ok(manifest)
133 }
134
135 pub(crate) async fn maybe_do_checkpoint(&mut self, manifest: &RegionManifest) {
139 if self
140 .checkpoint_task
141 .as_ref()
142 .is_some_and(|handle| !handle.is_finished())
143 {
144 return;
145 }
146
147 self.wait_for_pending_checkpoint().await;
150
151 if self.manifest_options.checkpoint_distance == 0 {
152 return;
153 }
154
155 let last_checkpoint_version = self.last_checkpoint_version();
156 if manifest.manifest_version - last_checkpoint_version
157 < self.manifest_options.checkpoint_distance
158 {
159 return;
160 }
161
162 let start_version = if last_checkpoint_version == 0 {
163 MIN_VERSION
166 } else {
167 last_checkpoint_version + 1
168 };
169 let end_version = manifest.manifest_version;
170 info!(
171 "Start doing checkpoint for region {}, compacted version: [{}, {}]",
172 self.inner.region_id(),
173 start_version,
174 end_version,
175 );
176
177 let checkpoint = RegionCheckpoint {
178 last_version: end_version,
179 compacted_actions: (end_version - start_version + 1) as usize,
180 checkpoint: Some(manifest.clone()),
181 };
182 self.do_checkpoint(checkpoint);
183 }
184
185 fn do_checkpoint(&mut self, checkpoint: RegionCheckpoint) {
186 let inner = self.inner.clone();
187 self.checkpoint_task = Some(common_runtime::spawn_global(async move {
188 inner.do_checkpoint(checkpoint).await;
189 }));
190 }
191
192 pub(crate) async fn wait_for_pending_checkpoint(&mut self) {
197 let Some(handle) = self.checkpoint_task.as_mut() else {
198 return;
199 };
200
201 #[cfg(test)]
202 self.pending_checkpoint_wait_started.notify_one();
203
204 let result = (&mut *handle).await;
205 self.checkpoint_task = None;
208
209 if let Err(e) = result {
210 warn!(e; "Failed to join checkpoint task for region {}", self.inner.region_id());
211 }
212 }
213
214 #[cfg(test)]
215 pub(crate) fn is_doing_checkpoint(&self) -> bool {
216 self.checkpoint_task
217 .as_ref()
218 .is_some_and(|handle| !handle.is_finished())
219 }
220
221 #[cfg(test)]
222 pub(crate) fn pending_checkpoint_wait_started(&self) -> Arc<Notify> {
223 self.pending_checkpoint_wait_started.clone()
224 }
225}