Skip to main content

linkrs_storage/engine/persistence/
checkpoint.rs

1use std::path::{Path, PathBuf};
2use std::time::{Duration, Instant, SystemTime};
3
4use linkrs_core::types::Timestamp;
5use linkrs_core::{StorageError, StorageResult};
6use linkrs_metrics::CheckpointTriggerReason;
7use linkrs_sync::checkpoint_manifest::{
8    CheckpointManifest, CheckpointManifestManager, IndexManifestRef,
9};
10use linkrs_transaction::wal::Lsn;
11
12use crate::engine::snapshot_manager::SnapshotOptions;
13use crate::persistence::dirty_page::IncrementalCheckpointMeta;
14
15use super::fs_util::{sync_directory, sync_tree};
16
17// Keep the codec symbols re-exported under the established `checkpoint::` path.
18pub use super::checkpoint_meta::{
19    CheckpointInfo, CHECKPOINT_FORMAT_VERSION, INCREMENTAL_CHECKPOINT_FORMAT_VERSION,
20};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct CheckpointStats {
24    pub checkpoint_id: u64,
25    pub data_flushed: u64,
26    pub wal_truncated: u64,
27    pub duration: Duration,
28    pub snapshot_created: bool,
29    pub checkpoint_seq: u64,
30    pub data_files_created: usize,
31    pub bytes_flushed: u64,
32    pub wal_files_truncated: usize,
33    pub trigger_reason: CheckpointTriggerReason,
34    /// MVCC snapshot timestamp the checkpoint flushed (the input timestamp).
35    /// Feeds the checkpoint watermark (`GcCoordinator`) so WAL-reclaim and
36    /// GC decisions can observe how fresh the last checkpoint is.
37    pub snapshot_timestamp: Timestamp,
38}
39
40#[derive(Debug, Clone)]
41pub struct CheckpointData {
42    /// Allocated vertex slots including deleted-but-unreclaimed entries:
43    /// storage scale, not a live count.
44    pub vertex_count: u64,
45    /// Live edge count on the stored leg.
46    pub edge_count: u64,
47    pub data_size: u64,
48}
49
50impl crate::engine::persistence_coordinator::PersistenceCoordinator {
51    pub(crate) fn cleanup_temporary_checkpoints(checkpoint_dir: &Path) -> StorageResult<()> {
52        for entry in std::fs::read_dir(checkpoint_dir)? {
53            let entry = entry?;
54            let path = entry.path();
55            let is_temporary = path
56                .file_name()
57                .and_then(|name| name.to_str())
58                .is_some_and(|name| name.starts_with("checkpoint_") && name.ends_with(".tmp"));
59            if is_temporary {
60                if path.is_dir() {
61                    std::fs::remove_dir_all(path)?;
62                } else {
63                    std::fs::remove_file(path)?;
64                }
65            }
66        }
67        Ok(())
68    }
69
70    pub(crate) fn latest_published_sequence(
71        manifest_manager: &CheckpointManifestManager,
72    ) -> StorageResult<u64> {
73        manifest_manager
74            .load_latest()
75            .map_err(StorageError::db_error)
76            .map(|manifest| manifest.map_or(0, |manifest| manifest.checkpoint_id))
77    }
78
79    /// A checkpoint directory is recoverable only after its combined manifest
80    /// is visible. Remove directories left behind by a crash after rename but
81    /// before that final publication fence.
82    pub(crate) fn cleanup_unpublished_checkpoints(
83        checkpoint_dir: &Path,
84        latest_published_sequence: u64,
85    ) -> StorageResult<()> {
86        for entry in std::fs::read_dir(checkpoint_dir)? {
87            let path = entry?.path();
88            if !path.is_dir() {
89                continue;
90            }
91            let Some(sequence) = path
92                .file_name()
93                .and_then(|name| name.to_str())
94                .and_then(|name| name.strip_prefix("checkpoint_"))
95                .and_then(|value| value.parse::<u64>().ok())
96            else {
97                continue;
98            };
99            if sequence > latest_published_sequence {
100                std::fs::remove_dir_all(path)?;
101            }
102        }
103        Ok(())
104    }
105
106    pub(crate) fn current_lsn(&self) -> Lsn {
107        match &self.wal_manager {
108            Some(wal) => wal.read().current_lsn(),
109            None => Lsn::ZERO,
110        }
111    }
112
113    pub(crate) fn wal_bytes_since(&self, base_lsn: Lsn) -> u64 {
114        self.current_lsn().offset_in_file(base_lsn)
115    }
116
117    pub fn should_flush(&self) -> bool {
118        let last_flush_lsn = *self.last_flush_lsn.read();
119        let last_flush = *self.last_flush_time.read();
120
121        self.wal_bytes_since(last_flush_lsn) >= self.config.checkpoint_threshold
122            || last_flush.elapsed() >= self.config.auto_flush_interval
123    }
124
125    pub fn should_checkpoint(&self) -> bool {
126        let last_checkpoint_lsn = *self.last_checkpoint_lsn.read();
127        let last_checkpoint = *self.last_checkpoint_time.read();
128        let wal_bytes_since_checkpoint = self.wal_bytes_since(last_checkpoint_lsn);
129
130        wal_bytes_since_checkpoint >= self.config.checkpoint_threshold
131            || wal_bytes_since_checkpoint >= self.config.max_wal_size
132            || last_checkpoint.elapsed() >= self.config.auto_checkpoint_interval
133    }
134
135    pub fn checkpoint_trigger_reason(&self) -> Option<CheckpointTriggerReason> {
136        let last_checkpoint_lsn = *self.last_checkpoint_lsn.read();
137        let last_checkpoint = *self.last_checkpoint_time.read();
138        let wal_bytes_since_checkpoint = self.wal_bytes_since(last_checkpoint_lsn);
139        if wal_bytes_since_checkpoint >= self.config.max_wal_size
140            || wal_bytes_since_checkpoint >= self.config.checkpoint_threshold
141        {
142            Some(CheckpointTriggerReason::WalSizeExceeded)
143        } else if last_checkpoint.elapsed() >= self.config.auto_checkpoint_interval {
144            Some(CheckpointTriggerReason::TimeSinceLastCheckpoint)
145        } else {
146            None
147        }
148    }
149
150    pub fn should_snapshot(&self) -> bool {
151        if !self.config.enable_snapshots {
152            return false;
153        }
154
155        if let Some(last_snapshot) = *self.last_snapshot_time.read() {
156            if let Ok(elapsed) = last_snapshot.elapsed() {
157                return elapsed >= self.config.snapshot_interval;
158            }
159        }
160
161        true
162    }
163
164    #[cfg(test)]
165    pub(crate) fn create_checkpoint(
166        &self,
167        flush_data: impl FnOnce(&Path, Timestamp) -> StorageResult<CheckpointData>,
168        timestamp: Timestamp,
169    ) -> StorageResult<CheckpointStats> {
170        self.create_checkpoint_with_reason(flush_data, timestamp, CheckpointTriggerReason::Explicit)
171    }
172
173    pub fn create_checkpoint_with_reason(
174        &self,
175        flush_data: impl FnOnce(&Path, Timestamp) -> StorageResult<CheckpointData>,
176        timestamp: Timestamp,
177        reason: CheckpointTriggerReason,
178    ) -> StorageResult<CheckpointStats> {
179        let result = self.create_checkpoint_inner(flush_data, timestamp, reason);
180        match &result {
181            Ok(stats) => {
182                *self.last_checkpoint_error.write() = None;
183                self.emit_storage_event(
184                    crate::engine::storage_events::StorageEvent::CheckpointCompleted {
185                        sequence: stats.checkpoint_seq,
186                        duration_ms: stats.duration.as_millis() as u64,
187                        data_flushed: stats.data_flushed,
188                        wal_truncated: stats.wal_truncated,
189                        snapshot_created: stats.snapshot_created,
190                    },
191                );
192            }
193            Err(error) => {
194                *self.last_checkpoint_error.write() = Some(error.to_string());
195                self.emit_storage_event(
196                    crate::engine::storage_events::StorageEvent::CheckpointFailed {
197                        sequence: None,
198                        error: error.to_string(),
199                    },
200                );
201            }
202        }
203        result
204    }
205
206    pub(crate) fn create_checkpoint_with_guard(
207        &self,
208        guard: crate::engine::persistence_coordinator::PersistenceStateGuard,
209        flush_data: impl FnOnce(&Path, Timestamp) -> StorageResult<CheckpointData>,
210        timestamp: Timestamp,
211        reason: CheckpointTriggerReason,
212    ) -> StorageResult<CheckpointStats> {
213        let result = self.create_checkpoint_inner_with_guard(guard, flush_data, timestamp, reason);
214        match &result {
215            Ok(stats) => {
216                *self.last_checkpoint_error.write() = None;
217                self.emit_storage_event(
218                    crate::engine::storage_events::StorageEvent::CheckpointCompleted {
219                        sequence: stats.checkpoint_seq,
220                        duration_ms: stats.duration.as_millis() as u64,
221                        data_flushed: stats.data_flushed,
222                        wal_truncated: stats.wal_truncated,
223                        snapshot_created: stats.snapshot_created,
224                    },
225                );
226            }
227            Err(error) => {
228                *self.last_checkpoint_error.write() = Some(error.to_string());
229                self.emit_storage_event(
230                    crate::engine::storage_events::StorageEvent::CheckpointFailed {
231                        sequence: None,
232                        error: error.to_string(),
233                    },
234                );
235            }
236        }
237        result
238    }
239
240    fn create_checkpoint_inner(
241        &self,
242        flush_data: impl FnOnce(&Path, Timestamp) -> StorageResult<CheckpointData>,
243        timestamp: Timestamp,
244        trigger_reason: CheckpointTriggerReason,
245    ) -> StorageResult<CheckpointStats> {
246        let guard = self
247            .enter_state(crate::engine::persistence_coordinator::PersistenceState::Checkpointing)?;
248        self.create_checkpoint_inner_with_guard(guard, flush_data, timestamp, trigger_reason)
249    }
250
251    fn create_checkpoint_inner_with_guard(
252        &self,
253        _guard: crate::engine::persistence_coordinator::PersistenceStateGuard,
254        flush_data: impl FnOnce(&Path, Timestamp) -> StorageResult<CheckpointData>,
255        timestamp: Timestamp,
256        trigger_reason: CheckpointTriggerReason,
257    ) -> StorageResult<CheckpointStats> {
258        let start = Instant::now();
259
260        let wal_lsn = {
261            match &self.wal_manager {
262                Some(wal) => {
263                    wal.read().sync()?;
264                    wal.read().durable_lsn()
265                }
266                None => Lsn::ZERO,
267            }
268        };
269
270        log::info!(
271            "Creating checkpoint at timestamp {}, LSN {}",
272            timestamp,
273            wal_lsn
274        );
275
276        let checkpoint = {
277            let published_sequence = Self::latest_published_sequence(&self.manifest_manager)?;
278            let mut cm = self.checkpoint_manager.write();
279            cm.adopt_published_sequence(published_sequence)
280                .map_err(|error| {
281                    StorageError::db_error(format!(
282                        "Failed to reconcile published checkpoints: {}",
283                        error
284                    ))
285                })?;
286            cm.prepare_checkpoint(timestamp, wal_lsn).map_err(|e| {
287                linkrs_core::StorageError::db_error(format!("Failed to create checkpoint: {}", e))
288            })?
289        };
290
291        self.emit_storage_event(
292            crate::engine::storage_events::StorageEvent::CheckpointStarted {
293                sequence: checkpoint.seq,
294            },
295        );
296
297        let checkpoint_dir = self
298            .config
299            .checkpoint_dir
300            .join(format!("checkpoint_{}", checkpoint.seq));
301        let temporary_dir = self
302            .config
303            .checkpoint_dir
304            .join(format!("checkpoint_{}.tmp", checkpoint.seq));
305        if temporary_dir.exists() {
306            std::fs::remove_dir_all(&temporary_dir)?;
307        }
308        if checkpoint_dir.exists() {
309            let is_published = self
310                .manifest_manager
311                .load(checkpoint.seq)
312                .map_err(|error| {
313                    StorageError::db_error(format!(
314                        "Failed to inspect existing checkpoint {}: {}",
315                        checkpoint.seq, error
316                    ))
317                })?
318                .is_some();
319            if is_published {
320                return Err(StorageError::invalid_operation(format!(
321                    "checkpoint {} already exists",
322                    checkpoint.seq
323                )));
324            }
325
326            std::fs::remove_dir_all(&checkpoint_dir)?;
327            sync_directory(&self.config.checkpoint_dir)?;
328        }
329        std::fs::create_dir(&temporary_dir)?;
330
331        self.fail_if_injected(
332            crate::engine::persistence_coordinator::PersistenceFaultPoint::CheckpointRedoBefore,
333        )?;
334        let data = flush_data(&temporary_dir, timestamp)?;
335        self.fail_if_injected(
336            crate::engine::persistence_coordinator::PersistenceFaultPoint::CheckpointIntentMid,
337        )?;
338        let files = Self::collect_checkpoint_files(&temporary_dir)?;
339        // Check if incremental checkpoint meta was produced by flush (Shadow Page CoW)
340        let incremental_meta_path = temporary_dir.join("incremental.meta");
341        if incremental_meta_path.exists() {
342            if let Ok(bytes) = std::fs::read(&incremental_meta_path) {
343                if let Ok(meta) = serde_json::from_slice::<IncrementalCheckpointMeta>(&bytes) {
344                    self.save_checkpoint_metadata_extended(
345                        &temporary_dir,
346                        &checkpoint,
347                        &data,
348                        &files,
349                        Some(&meta),
350                    )?;
351                    // Also keep incremental.meta file for load_incremental_checkpoint_metadata (already there)
352                } else {
353                    self.save_checkpoint_metadata(&temporary_dir, &checkpoint, &data, &files)?;
354                }
355            } else {
356                self.save_checkpoint_metadata(&temporary_dir, &checkpoint, &data, &files)?;
357            }
358        } else {
359            self.save_checkpoint_metadata(&temporary_dir, &checkpoint, &data, &files)?;
360        }
361        self.fail_if_injected(
362            crate::engine::persistence_coordinator::PersistenceFaultPoint::CheckpointCommitMid,
363        )?;
364        sync_tree(&temporary_dir)?;
365        std::fs::rename(&temporary_dir, &checkpoint_dir)?;
366        sync_directory(&self.config.checkpoint_dir)?;
367        self.fail_if_injected(
368            crate::engine::persistence_coordinator::PersistenceFaultPoint::CheckpointFsyncAfter,
369        )?;
370
371        let snapshot_created = if self.should_snapshot() {
372            *self.state.write() =
373                crate::engine::persistence_coordinator::PersistenceState::Snapshotting;
374            if let Some(ref snapshot_manager) = self.snapshot_manager {
375                let snapshot_options = SnapshotOptions::default();
376                match snapshot_manager.create_snapshot(
377                    crate::engine::snapshot_manager::CreateSnapshotParams {
378                        source_dir: checkpoint_dir.clone(),
379                        snapshot_id: checkpoint.seq,
380                        vertex_count: data.vertex_count,
381                        edge_count: data.edge_count,
382                        checkpoint_seq: checkpoint.seq,
383                        wal_lsn: wal_lsn.into(),
384                        options: snapshot_options,
385                    },
386                ) {
387                    Ok(_) => {
388                        *self.last_snapshot_time.write() = Some(SystemTime::now());
389                        *self.last_snapshot_error.write() = None;
390                        true
391                    }
392                    Err(e) => {
393                        log::error!("Failed to create snapshot: {}", e);
394                        *self.last_snapshot_error.write() = Some(e.to_string());
395                        false
396                    }
397                }
398            } else {
399                false
400            }
401        } else {
402            false
403        };
404
405        self.fail_if_injected(
406            crate::engine::persistence_coordinator::PersistenceFaultPoint::CheckpointVisibilityPublish,
407        )?;
408        match self.publish_checkpoint_manifest(&checkpoint, &data, &checkpoint_dir, wal_lsn) {
409            Ok((snapshot_ts, safe_lsn)) => {
410                // Refresh the checkpoint watermark observed by GC passes.
411                // In-memory only, so it cannot fail the checkpoint: the
412                // durable twin of the same values is the published manifest,
413                // which passes lazily reload after a restart.
414                self.refresh_checkpoint_watermark(snapshot_ts, safe_lsn);
415            }
416            Err(error) => return Err(error),
417        }
418
419        {
420            let mut cm = self.checkpoint_manager.write();
421            cm.publish_checkpoint(&checkpoint).map_err(|e| {
422                StorageError::db_error(format!("Failed to publish checkpoint: {}", e))
423            })?;
424        }
425
426        if let Some(ref wal) = self.wal_manager {
427            wal.read().set_checkpoint_seq(checkpoint.seq)?;
428        }
429
430        self.mark_checkpointed(wal_lsn);
431
432        let safe_wal_lsn = if let Some(ref wal) = self.wal_manager {
433            let manifest_safe_lsn = self.manifest_manager.latest_safe_lsn().map_err(|error| {
434                StorageError::db_error(format!("Failed to get safe LSN: {}", error))
435            })?;
436            let outbox_safe_lsn = self
437                .outbox_frontier_provider
438                .read()
439                .as_ref()
440                .map(|provider| provider())
441                .transpose()?
442                .flatten()
443                .map(|lsn| lsn.get());
444            let safe_lsn = outbox_safe_lsn.map_or(manifest_safe_lsn.get(), |outbox| {
445                manifest_safe_lsn.get().min(outbox)
446            });
447            let safe_wal_lsn = Lsn::new(safe_lsn);
448            wal.read().truncate(safe_wal_lsn)?;
449            if safe_wal_lsn != Lsn::ZERO {
450                self.notify_wal_truncated(safe_wal_lsn.into());
451            }
452            safe_wal_lsn
453        } else {
454            wal_lsn
455        };
456
457        if snapshot_created {
458            self.emit_storage_event(
459                crate::engine::storage_events::StorageEvent::SnapshotCreated {
460                    sequence: checkpoint.seq,
461                },
462            );
463        }
464
465        let stats = CheckpointStats {
466            checkpoint_id: checkpoint.seq,
467            data_flushed: data.data_size,
468            wal_truncated: safe_wal_lsn.into(),
469            duration: start.elapsed(),
470            snapshot_created,
471            checkpoint_seq: checkpoint.seq,
472            data_files_created: files.len(),
473            bytes_flushed: data.data_size,
474            wal_files_truncated: if safe_wal_lsn != Lsn::ZERO { 1 } else { 0 },
475            trigger_reason,
476            snapshot_timestamp: timestamp,
477        };
478        *self.last_checkpoint_stats.write() = Some(stats.clone());
479
480        log::info!(
481            "Checkpoint {} completed in {:?}",
482            checkpoint.seq,
483            stats.duration
484        );
485
486        Ok(stats)
487    }
488
489    pub fn load_latest_checkpoint(
490        &self,
491        load_data: impl FnOnce(&Path) -> StorageResult<()>,
492    ) -> StorageResult<Option<CheckpointInfo>> {
493        self.fail_if_injected(
494            crate::engine::persistence_coordinator::PersistenceFaultPoint::RecoveryScan,
495        )?;
496        let checkpoints_dir = &self.config.checkpoint_dir;
497
498        if !checkpoints_dir.exists() {
499            return Ok(None);
500        }
501
502        let published_manifest = self
503            .manifest_manager
504            .load_latest()
505            .map_err(StorageError::db_error)?;
506        let Some(published_manifest) = published_manifest else {
507            let manifest_dir = self.config.checkpoint_dir.join("manifests");
508            if manifest_dir.exists() {
509                if let Ok(mut entries) = std::fs::read_dir(&manifest_dir) {
510                    if entries.any(|e| e.is_ok()) {
511                        return Err(StorageError::deserialize_error(
512                            "Published manifest exists but failed validation. \
513                             Checkpoint files or manifests may be corrupted."
514                                .to_string(),
515                        ));
516                    }
517                }
518            }
519            return Ok(None);
520        };
521        let published_checkpoint = published_manifest.storage_snapshot.checkpoint_seq;
522        let mut checkpoints: Vec<(u64, PathBuf)> = std::fs::read_dir(checkpoints_dir)?
523            .filter_map(|entry| entry.ok())
524            .filter_map(|entry| {
525                let path = entry.path();
526                if path.is_dir() && path.join("checkpoint.meta").is_file() {
527                    let name = path.file_name()?.to_string_lossy();
528                    if name.starts_with("checkpoint_") {
529                        let id: u64 = name.trim_start_matches("checkpoint_").parse().ok()?;
530                        (id == published_checkpoint).then_some((id, path))
531                    } else {
532                        None
533                    }
534                } else {
535                    None
536                }
537            })
538            .collect();
539
540        checkpoints.sort_by_key(|(id, _)| std::cmp::Reverse(*id));
541
542        if let Some((_, checkpoint_path)) = checkpoints.first() {
543            let (info, files) = self.load_checkpoint_metadata(checkpoint_path)?;
544            Self::verify_checkpoint_files(checkpoint_path, &files)?;
545
546            load_data(checkpoint_path)?;
547
548            if let Some(ref wal) = self.wal_manager {
549                wal.read().set_checkpoint_seq(info.checkpoint_id)?;
550                wal.read().set_recovery_baseline_lsn(info.lsn)?;
551            }
552
553            return Ok(Some(info));
554        }
555
556        Ok(None)
557    }
558
559    /// Publish a combined checkpoint manifest that atomically references the
560    /// storage snapshot, outbox snapshot (if provided), and index manifests.
561    ///
562    /// Returns the published watermark pair `(snapshot_ts, safe_lsn)` so the
563    /// caller can refresh the checkpoint watermark observed by
564    /// `GcCoordinator`. The snapshot timestamp is the checkpoint input: the
565    /// storage checkpoint path is the authoritative producer of checkpoint
566    /// snapshots (the gated transaction-layer checkpoint transaction is a
567    /// separate path and is not consulted here).
568    fn publish_checkpoint_manifest(
569        &self,
570        checkpoint: &linkrs_transaction::wal::Checkpoint,
571        data: &CheckpointData,
572        checkpoint_dir: &Path,
573        wal_lsn: Lsn,
574    ) -> StorageResult<(Timestamp, linkrs_core::types::CommitLsn)> {
575        let storage_snapshot_ref = CheckpointManifest::storage_snapshot_from_directory(
576            checkpoint_dir,
577            checkpoint.seq,
578            data.vertex_count,
579            data.edge_count,
580        )
581        .map_err(|error| {
582            StorageError::db_error(format!(
583                "Failed to build storage snapshot reference: {}",
584                error
585            ))
586        })?;
587
588        let storage_lsn = linkrs_core::types::CommitLsn::new(wal_lsn.into());
589        let work_dir = self
590            .config
591            .data_dir
592            .parent()
593            .unwrap_or(&self.config.data_dir);
594        let outbox_snapshot = linkrs_sync::find_latest_snapshot_at_or_before(
595            &work_dir.join("outbox_snapshots"),
596            storage_lsn.get(),
597        )
598        .map(|snapshot| CheckpointManifest::outbox_snapshot_from(&snapshot));
599        let index_manifests = Self::collect_index_manifest_refs(checkpoint_dir)?;
600
601        let outbox_enabled = work_dir.join("outbox/outbox.sqlite").exists()
602            || work_dir.join("outbox_snapshots").is_dir();
603
604        let manifest = if outbox_enabled {
605            CheckpointManifest::new_with_outbox(
606                checkpoint.seq,
607                storage_lsn,
608                storage_snapshot_ref,
609                outbox_snapshot,
610                index_manifests,
611            )
612            .map_err(StorageError::db_error)?
613        } else {
614            CheckpointManifest::new(
615                checkpoint.seq,
616                storage_lsn,
617                storage_snapshot_ref,
618                outbox_snapshot,
619                index_manifests,
620            )
621            .map_err(StorageError::db_error)?
622        };
623        let manifest = manifest
624            .with_snapshot_timestamp(checkpoint.timestamp)
625            .map_err(StorageError::db_error)?;
626
627        self.manifest_manager.publish(&manifest).map_err(|error| {
628            StorageError::db_error(format!("Failed to publish manifest: {}", error))
629        })?;
630
631        log::info!(
632            "Published checkpoint manifest {} with safe LSN {}",
633            checkpoint.seq,
634            manifest.safe_lsn
635        );
636
637        Ok((checkpoint.timestamp, manifest.safe_lsn))
638    }
639
640    fn collect_index_manifest_refs(root: &Path) -> StorageResult<Vec<IndexManifestRef>> {
641        fn visit(directory: &Path, refs: &mut Vec<IndexManifestRef>) -> StorageResult<()> {
642            for entry in std::fs::read_dir(directory)? {
643                let path = entry?.path();
644                if path.is_dir() {
645                    visit(&path, refs)?;
646                    continue;
647                }
648                if path
649                    .parent()
650                    .and_then(Path::file_name)
651                    .and_then(|name| name.to_str())
652                    != Some("native_index_manifests")
653                    || path.extension().and_then(|extension| extension.to_str()) != Some("json")
654                {
655                    continue;
656                }
657                let manifest = crate::index::manifest::IndexManifest::load(&path)?;
658                let bytes = std::fs::read(&path)?;
659                refs.push(IndexManifestRef {
660                    space_id: manifest.space_id,
661                    index_id: manifest.index_id,
662                    generation: manifest.generation,
663                    path,
664                    size_bytes: bytes.len() as u64,
665                    checksum: crc32fast::hash(&bytes),
666                });
667            }
668            Ok(())
669        }
670
671        let mut refs = Vec::new();
672        visit(root, &mut refs)?;
673        refs.sort_by_key(|reference| reference.index_id);
674        Ok(refs)
675    }
676}