1use std::collections::HashMap;
16use std::sync::Arc;
17
18use common_datasource::compression::CompressionType;
19use common_telemetry::{debug, warn};
20use object_store::{ErrorKind, ObjectStore};
21use serde::{Deserialize, Serialize};
22use snafu::{OptionExt, ResultExt};
23use store_api::ManifestVersion;
24
25use crate::cache::manifest_cache::ManifestCache;
26use crate::error::{
27 CompressObjectSnafu, DecompressObjectSnafu, ManifestCheckpointVanishedSnafu, OpenDalSnafu,
28 Result, SerdeJsonSnafu, Utf8Snafu,
29};
30use crate::manifest::storage::size_tracker::Tracker;
31use crate::manifest::storage::utils::{get_from_cache_decoded, write_and_put_cache};
32use crate::manifest::storage::{
33 FALL_BACK_COMPRESS_TYPE, LAST_CHECKPOINT_FILE, checkpoint_checksum, checkpoint_file, gen_path,
34 verify_checksum,
35};
36
37#[derive(Serialize, Deserialize, Debug)]
38pub(crate) struct CheckpointMetadata {
39 pub size: usize,
40 pub version: ManifestVersion,
42 pub checksum: Option<u32>,
43 pub extend_metadata: HashMap<String, String>,
44}
45
46impl CheckpointMetadata {
47 fn encode(&self) -> Result<Vec<u8>> {
48 Ok(serde_json::to_string(self)
49 .context(SerdeJsonSnafu)?
50 .into_bytes())
51 }
52
53 fn decode(bs: &[u8]) -> Result<Self> {
54 let data = std::str::from_utf8(bs).context(Utf8Snafu)?;
55
56 serde_json::from_str(data).context(SerdeJsonSnafu)
57 }
58}
59
60#[derive(Debug, Clone)]
62pub(crate) struct CheckpointStorage<T: Tracker> {
63 object_store: ObjectStore,
64 compress_type: CompressionType,
65 path: String,
66 manifest_cache: Option<ManifestCache>,
67 size_tracker: Arc<T>,
68}
69
70impl<T: Tracker> CheckpointStorage<T> {
71 pub fn new(
72 path: String,
73 object_store: ObjectStore,
74 compress_type: CompressionType,
75 manifest_cache: Option<ManifestCache>,
76 size_tracker: Arc<T>,
77 ) -> Self {
78 Self {
79 object_store,
80 compress_type,
81 path,
82 manifest_cache,
83 size_tracker,
84 }
85 }
86
87 pub(crate) fn last_checkpoint_path(&self) -> String {
90 format!("{}{}", self.path, LAST_CHECKPOINT_FILE)
91 }
92
93 fn checkpoint_file_path(&self, version: ManifestVersion) -> String {
95 gen_path(&self.path, &checkpoint_file(version), self.compress_type)
96 }
97
98 pub(crate) async fn load_checkpoint(
99 &mut self,
100 metadata: CheckpointMetadata,
101 ) -> Result<Option<(ManifestVersion, Vec<u8>)>> {
102 let version = metadata.version;
103 let path = self.checkpoint_file_path(version);
104
105 if let Some(data) =
107 get_from_cache_decoded(self.manifest_cache.as_ref(), &path, self.compress_type).await
108 {
109 verify_checksum(&data, metadata.checksum)?;
110 self.record_cached_checkpoint_size(version, &path).await;
111 return Ok(Some((version, data)));
112 }
113
114 let checkpoint_data = match self.object_store.read(&path).await {
117 Ok(checkpoint) => {
118 let checkpoint_size = checkpoint.len();
119 let decompress_data = self
120 .compress_type
121 .decode(checkpoint.clone())
122 .await
123 .with_context(|_| DecompressObjectSnafu {
124 compress_type: self.compress_type,
125 path: path.clone(),
126 })?;
127 verify_checksum(&decompress_data, metadata.checksum)?;
128 self.size_tracker.record(version, checkpoint_size as u64);
130 if let Some(cache) = &self.manifest_cache {
132 cache.put_file(path, checkpoint.to_vec()).await;
133 }
134 Ok(Some(decompress_data))
135 }
136 Err(e) => {
137 if e.kind() == ErrorKind::NotFound {
138 if self.compress_type != FALL_BACK_COMPRESS_TYPE {
139 let fall_back_path = gen_path(
140 &self.path,
141 &checkpoint_file(version),
142 FALL_BACK_COMPRESS_TYPE,
143 );
144 debug!(
145 "Failed to load checkpoint from path: {}, fall back to path: {}",
146 path, fall_back_path
147 );
148
149 if let Some(data) = get_from_cache_decoded(
151 self.manifest_cache.as_ref(),
152 &fall_back_path,
153 FALL_BACK_COMPRESS_TYPE,
154 )
155 .await
156 {
157 verify_checksum(&data, metadata.checksum)?;
158 self.record_cached_checkpoint_size(version, &fall_back_path)
159 .await;
160 return Ok(Some((version, data)));
161 }
162
163 match self.object_store.read(&fall_back_path).await {
164 Ok(checkpoint) => {
165 let checkpoint_size = checkpoint.len();
166 let decompress_data = FALL_BACK_COMPRESS_TYPE
167 .decode(checkpoint.clone())
168 .await
169 .with_context(|_| DecompressObjectSnafu {
170 compress_type: FALL_BACK_COMPRESS_TYPE,
171 path: fall_back_path.clone(),
172 })?;
173 verify_checksum(&decompress_data, metadata.checksum)?;
174 self.size_tracker.record(version, checkpoint_size as u64);
175 if let Some(cache) = &self.manifest_cache {
177 cache.put_file(fall_back_path, checkpoint.to_vec()).await;
178 }
179 Ok(Some(decompress_data))
180 }
181 Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
182 Err(e) => return Err(e).context(OpenDalSnafu),
183 }
184 } else {
185 Ok(None)
186 }
187 } else {
188 Err(e).context(OpenDalSnafu)
189 }
190 }
191 }?;
192 Ok(checkpoint_data.map(|data| (version, data)))
193 }
194
195 pub async fn load_last_checkpoint(&mut self) -> Result<Option<(ManifestVersion, Vec<u8>)>> {
198 let Some(metadata) = self.read_last_checkpoint_metadata().await? else {
199 return Ok(None);
200 };
201 self.load_checkpoint(metadata).await
202 }
203
204 pub(crate) async fn load_listed_checkpoint(
212 &self,
213 version: ManifestVersion,
214 compress_type: CompressionType,
215 ) -> Result<(ManifestVersion, Vec<u8>)> {
216 let checksum = self
217 .read_last_checkpoint_metadata()
218 .await?
219 .filter(|metadata| metadata.version == version)
220 .and_then(|metadata| metadata.checksum);
221 let mut storage = Self::new(
222 self.path.clone(),
223 self.object_store.clone(),
224 compress_type,
225 self.manifest_cache.clone(),
226 self.size_tracker.clone(),
227 );
228 storage
229 .load_checkpoint(CheckpointMetadata {
230 size: 0,
231 version,
232 checksum,
233 extend_metadata: HashMap::new(),
234 })
235 .await?
236 .context(ManifestCheckpointVanishedSnafu {
237 version,
238 manifest_dir: &self.path,
239 })
240 }
241
242 pub(crate) async fn read_last_checkpoint_metadata(&self) -> Result<Option<CheckpointMetadata>> {
244 let last_checkpoint_path = self.last_checkpoint_path();
245
246 let last_checkpoint_data = match self.object_store.read(&last_checkpoint_path).await {
248 Ok(data) => data.to_vec(),
249 Err(e) if e.kind() == ErrorKind::NotFound => {
250 return Ok(None);
251 }
252 Err(e) => {
253 return Err(e).context(OpenDalSnafu)?;
254 }
255 };
256
257 let checkpoint_metadata = CheckpointMetadata::decode(&last_checkpoint_data)?;
258
259 debug!(
260 "Load checkpoint in path: {}, metadata: {:?}",
261 last_checkpoint_path, checkpoint_metadata
262 );
263
264 Ok(Some(checkpoint_metadata))
265 }
266
267 async fn record_cached_checkpoint_size(&self, version: ManifestVersion, path: &str) {
269 match self.object_store.stat(path).await {
270 Ok(metadata) => self.size_tracker.record(version, metadata.content_length()),
271 Err(error) => warn!(error; "Failed to account for cached checkpoint {}", path),
272 }
273 }
274
275 pub(crate) async fn record_checkpoint_size(&self, version: ManifestVersion) -> Result<()> {
277 let path = self.checkpoint_file_path(version);
278 let metadata = match self.object_store.stat(&path).await {
279 Ok(metadata) => metadata,
280 Err(error)
281 if error.kind() == ErrorKind::NotFound
282 && self.compress_type != FALL_BACK_COMPRESS_TYPE =>
283 {
284 let path = gen_path(
285 &self.path,
286 &checkpoint_file(version),
287 FALL_BACK_COMPRESS_TYPE,
288 );
289 self.object_store.stat(&path).await.context(OpenDalSnafu)?
290 }
291 Err(error) => return Err(error).context(OpenDalSnafu),
292 };
293 self.size_tracker.record(version, metadata.content_length());
294 Ok(())
295 }
296
297 pub(crate) async fn save_checkpoint(
299 &self,
300 version: ManifestVersion,
301 bytes: &[u8],
302 ) -> Result<()> {
303 let path = self.checkpoint_file_path(version);
304 let data = self
305 .compress_type
306 .encode(bytes)
307 .await
308 .context(CompressObjectSnafu {
309 compress_type: self.compress_type,
310 path: &path,
311 })?;
312 let checkpoint_size = data.len();
313 let checksum = checkpoint_checksum(bytes);
314
315 write_and_put_cache(
316 &self.object_store,
317 self.manifest_cache.as_ref(),
318 &path,
319 data,
320 )
321 .await?;
322
323 let last_checkpoint_path = self.last_checkpoint_path();
325
326 let checkpoint_metadata = CheckpointMetadata {
327 size: bytes.len(),
328 version,
329 checksum: Some(checksum),
330 extend_metadata: HashMap::new(),
331 };
332
333 debug!(
334 "Save checkpoint in path: {}, metadata: {:?}",
335 last_checkpoint_path, checkpoint_metadata
336 );
337
338 let bytes = checkpoint_metadata.encode()?;
339 self.object_store
340 .write(&last_checkpoint_path, bytes)
341 .await
342 .context(OpenDalSnafu)?;
343
344 self.size_tracker.record(version, checkpoint_size as u64);
345 Ok(())
346 }
347}
348
349#[cfg(test)]
350impl<T: Tracker> CheckpointStorage<T> {
351 pub async fn write_last_checkpoint(
352 &self,
353 version: ManifestVersion,
354 bytes: &[u8],
355 ) -> Result<()> {
356 let path = self.checkpoint_file_path(version);
357 let data = self
358 .compress_type
359 .encode(bytes)
360 .await
361 .context(CompressObjectSnafu {
362 compress_type: self.compress_type,
363 path: &path,
364 })?;
365
366 let checkpoint_size = data.len();
367
368 self.object_store
369 .write(&path, data)
370 .await
371 .context(OpenDalSnafu)?;
372
373 self.size_tracker.record(version, checkpoint_size as u64);
374
375 let last_checkpoint_path = self.last_checkpoint_path();
376 let checkpoint_metadata = CheckpointMetadata {
377 size: bytes.len(),
378 version,
379 checksum: Some(1218259706),
380 extend_metadata: HashMap::new(),
381 };
382
383 debug!(
384 "Rewrite checkpoint in path: {}, metadata: {:?}",
385 last_checkpoint_path, checkpoint_metadata
386 );
387
388 let bytes = checkpoint_metadata.encode()?;
389
390 self.object_store
392 .write(&last_checkpoint_path, bytes.clone())
393 .await
394 .context(OpenDalSnafu)?;
395 Ok(())
396 }
397
398 pub fn set_compress_type(&mut self, compress_type: CompressionType) {
399 self.compress_type = compress_type;
400 }
401}