1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use std::path::{Path, PathBuf};
use crate::common::mmap::Advice::Normal;
use crate::common::mmap::AdviceSetting;
use crate::common::types::PointOffsetType;
use crate::common::universal_io::{
CachedReadFs, OkNotFound, OpenOptions, Populate, UniversalRead, UniversalReadFs,
};
use super::ReadOnlyAppendableIdTracker;
use crate::segment::common::operation_error::OperationResult;
use crate::segment::id_tracker::mutable_id_tracker::mappings_storage::mappings_path;
use crate::segment::id_tracker::mutable_id_tracker::versions_storage::versions_path;
use crate::segment::id_tracker::point_mappings::PointMappings;
impl<S: UniversalRead> ReadOnlyAppendableIdTracker<S> {
fn open_options() -> OpenOptions {
OpenOptions {
writeable: false,
need_sequential: false,
populate: Populate::PreferBackground,
advice: AdviceSetting::Advice(Normal),
}
}
/// Schedule background prefetch of the mappings log and versions file that
/// [`open`](Self::open) reads via [`live_reload`](Self::live_reload).
///
/// Either file may not exist yet — the writer only creates them once it
/// flushes the first point, and [`open`](Self::open) treats a missing file
/// as an empty storage — so absence is tolerated here too.
pub fn preopen(fs: &impl CachedReadFs<File = S>, segment_path: &Path) -> OperationResult<()> {
let options = Self::open_options();
fs.schedule_prefetch(&mappings_path(segment_path), Some(options), None)
.ok_not_found()?;
fs.schedule_prefetch(&versions_path(segment_path), Some(options), None)
.ok_not_found()?;
Ok(())
}
/// Open a read-only view over the appendable ID tracker data at `segment_path`, threading every
/// file open through the filesystem handle `fs`.
///
/// The mappings and versions files may not exist yet — the writer only creates them once it
/// flushes the first point, exactly as
/// [`MutableIdTracker::open`](crate::id_tracker::mutable_id_tracker::MutableIdTracker::open)
/// tolerates. A missing file is treated as an empty storage (not an error) and opened lazily
/// once it appears.
///
/// Unlike `MutableIdTracker::open` this never writes to the storage. The initial state is loaded
/// by running the same reconciliation as [`Self::live_reload`] from an empty tracker: the whole
/// mappings log and versions file are consumed, applying only committed points (a partial
/// trailing entry is simply not consumed and picked up on a later reload).
pub fn open(
fs: &S::Fs,
segment_path: impl Into<PathBuf>,
deferred_internal_id: Option<PointOffsetType>,
) -> OperationResult<Self> {
// The tracker keeps a filesystem handle to re-open the append-only
// files on later reloads, so it must retain the *raw* backend — a
// caching wrapper's snapshot goes stale as soon as the writer
// appends. The bootstrap below consequently opens through the raw
// fs too, bypassing any prefetch pool.
let mut tracker = Self {
segment_path: segment_path.into(),
fs: fs.clone(),
internal_to_version: Vec::new(),
mappings: PointMappings::new(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
deferred_internal_id,
),
pending_inserts: Default::default(),
mappings_read_to: 0,
// Opened lazily by `live_reload`: the files may not exist until the writer flushes.
mappings_file: None,
versions_file: None,
};
// Load the existing data the same way a live-reload consumes appended data. The reported
// delta (the whole committed set as inserts) is irrelevant for an initial open.
tracker.live_reload()?;
#[cfg(debug_assertions)]
tracker.mappings.assert_mappings();
Ok(tracker)
}
/// Open the file at `path` read-only, returning `None` if it does not exist.
///
/// A read-only follower's mappings/versions files are absent while empty (the writer never
/// writes an empty file), so `NotFound` means an empty storage rather than an error. We open
/// directly and map `NotFound` to `None` instead of probing with `exists` first, to avoid a
/// second round-trip on object storage. Lazy backends (e.g. S3) touch the object only on the
/// first read, so a missing object can instead surface as `NotFound` from a later `len`/`read`
/// — `live_reload` tolerates that case too.
pub(super) fn try_open(fs: &S::Fs, path: &Path) -> OperationResult<Option<S>> {
let options = Self::open_options();
Ok(fs.open(path, options, Default::default()).ok_not_found()?)
}
}