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 self.manifest_store.retain_manifest_size_after(version);
83
84 if let Err(e) = self.manifest_store.delete_until(version, true).await {
85 warn!(e; "Failed to delete manifest actions until version {} for region {}, leftover files will be ignored on recovery", version, region_id);
86 }
87
88 info!(
89 "Checkpoint for region {} success, version: {}",
90 region_id, version
91 );
92 }
93
94 fn region_id(&self) -> RegionId {
95 self.region_id
96 }
97}
98
99impl Checkpointer {
100 pub(crate) fn new(
101 region_id: RegionId,
102 manifest_options: RegionManifestOptions,
103 manifest_store: ManifestObjectStore,
104 last_checkpoint_version: ManifestVersion,
105 ) -> Self {
106 Self {
107 manifest_options,
108 inner: Arc::new(Inner {
109 region_id,
110 manifest_store,
111 last_checkpoint_version: AtomicU64::new(last_checkpoint_version),
112 }),
113 checkpoint_task: None,
114 #[cfg(test)]
115 pending_checkpoint_wait_started: Arc::new(Notify::new()),
116 }
117 }
118
119 pub(crate) fn last_checkpoint_version(&self) -> ManifestVersion {
120 self.inner.last_checkpoint_version.load(Ordering::Relaxed)
121 }
122
123 pub(crate) fn update_manifest_removed_files(
126 &self,
127 mut manifest: RegionManifest,
128 ) -> Result<RegionManifest> {
129 let opt = &self.manifest_options.remove_file_options;
130
131 manifest.removed_files.evict_old_removed_files(opt)?;
132
133 Ok(manifest)
136 }
137
138 pub(crate) async fn maybe_do_checkpoint(&mut self, manifest: &RegionManifest) {
142 if self
143 .checkpoint_task
144 .as_ref()
145 .is_some_and(|handle| !handle.is_finished())
146 {
147 return;
148 }
149
150 self.wait_for_pending_checkpoint().await;
153
154 if self.manifest_options.checkpoint_distance == 0 {
155 return;
156 }
157
158 let last_checkpoint_version = self.last_checkpoint_version();
159 if manifest.manifest_version - last_checkpoint_version
160 < self.manifest_options.checkpoint_distance
161 {
162 return;
163 }
164
165 let start_version = if last_checkpoint_version == 0 {
166 MIN_VERSION
169 } else {
170 last_checkpoint_version + 1
171 };
172 let end_version = manifest.manifest_version;
173 info!(
174 "Start doing checkpoint for region {}, compacted version: [{}, {}]",
175 self.inner.region_id(),
176 start_version,
177 end_version,
178 );
179
180 let checkpoint = RegionCheckpoint {
181 last_version: end_version,
182 compacted_actions: (end_version - start_version + 1) as usize,
183 checkpoint: Some(manifest.clone()),
184 };
185 self.do_checkpoint(checkpoint);
186 }
187
188 fn do_checkpoint(&mut self, checkpoint: RegionCheckpoint) {
189 let inner = self.inner.clone();
190 self.checkpoint_task = Some(common_runtime::spawn_global(async move {
191 inner.do_checkpoint(checkpoint).await;
192 }));
193 }
194
195 pub(crate) async fn wait_for_pending_checkpoint(&mut self) {
200 let Some(handle) = self.checkpoint_task.as_mut() else {
201 return;
202 };
203
204 #[cfg(test)]
205 self.pending_checkpoint_wait_started.notify_one();
206
207 let result = (&mut *handle).await;
208 self.checkpoint_task = None;
211
212 if let Err(e) = result {
213 warn!(e; "Failed to join checkpoint task for region {}", self.inner.region_id());
214 }
215 }
216
217 #[cfg(test)]
218 pub(crate) fn is_doing_checkpoint(&self) -> bool {
219 self.checkpoint_task
220 .as_ref()
221 .is_some_and(|handle| !handle.is_finished())
222 }
223
224 #[cfg(test)]
225 pub(crate) fn pending_checkpoint_wait_started(&self) -> Arc<Notify> {
226 self.pending_checkpoint_wait_started.clone()
227 }
228}