pub struct SnapshotEngine { /* private fields */ }Expand description
A ref-free engine for deterministic immutable collection roots.
The engine writes and reads Git objects but never creates a commit, updates a ref, consults repository history, or reads the clock. Callers are responsible for retaining returned roots, for example through an external content store or the named collection adapter.
Implementations§
Source§impl SnapshotEngine
impl SnapshotEngine
Sourcepub fn open(path: impl AsRef<Path>) -> Result<Self>
pub fn open(path: impl AsRef<Path>) -> Result<Self>
Opens an existing bare or non-bare Git object database without reading a collection ref.
Examples found in repository?
79fn mutate(
80 input: &Path,
81 repository: &Path,
82 build_report: &Path,
83 fraction: f64,
84 output: &Path,
85 sample_stable: bool,
86) -> Result<(), Box<dyn std::error::Error>> {
87 let spec = read_spec(input)?;
88 if !(0.0..=1.0).contains(&fraction) || fraction == 0.0 {
89 return Err("mutation fraction must be greater than zero and at most one".into());
90 }
91 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
92 let points = make_points(&vectors);
93 let count = ((spec.point_count as f64 * fraction).round() as usize).clamp(1, spec.point_count);
94 let mut changed = if sample_stable {
95 sample_stable_points(points, count)?
96 } else {
97 points[..count].to_vec()
98 };
99 for point in &mut changed {
100 point.vector[0] += 0.001;
101 }
102 let build: serde_json::Value = serde_json::from_slice(&fs::read(build_report)?)?;
103 let root = build
104 .get("root")
105 .and_then(serde_json::Value::as_str)
106 .ok_or("build report root is missing")?;
107 let engine = SnapshotEngine::open(repository)?;
108
109 let upsert_started = Instant::now();
110 let upserted = engine.apply(
111 root,
112 changed.into_iter().map(SnapshotMutation::upsert).collect(),
113 )?;
114 let upsert_us = micros(upsert_started);
115 let delete_started = Instant::now();
116 let deleted = engine.apply(
117 root,
118 vec![SnapshotMutation::delete_ids(
119 (0..count).map(|id| PointId::from(id as u64)),
120 )],
121 )?;
122 let delete_us = micros(delete_started);
123 fs::write(
124 output,
125 serde_json::to_vec_pretty(&json!({
126 "schema_version": 1,
127 "root": root,
128 "fraction": fraction,
129 "points": count,
130 "sample_stable": sample_stable,
131 "upsert_us": upsert_us,
132 "delete_us": delete_us,
133 "upsert_root": upserted.root(),
134 "delete_root": deleted.root(),
135 "on_disk_bytes_after": directory_bytes(repository)?,
136 }))?,
137 )?;
138 Ok(())
139}
140
141fn sample_stable_points(
142 points: Vec<Point>,
143 count: usize,
144) -> Result<Vec<Point>, Box<dyn std::error::Error>> {
145 let mut sample_order = points
146 .iter()
147 .map(|point| Ok((uint_id_digest(&point.id)?, point.id.clone())))
148 .collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()?;
149 sample_order.sort();
150 let sample_ids = sample_order
151 .into_iter()
152 .take(8_192.min(points.len()))
153 .map(|(_, id)| id)
154 .collect::<HashSet<_>>();
155 let selected = points
156 .into_iter()
157 .filter(|point| !sample_ids.contains(&point.id))
158 .take(count)
159 .collect::<Vec<_>>();
160 if selected.len() != count {
161 return Err(format!(
162 "sample-stable mutation requested {count} points but only {} are outside the training sample",
163 selected.len()
164 )
165 .into());
166 }
167 Ok(selected)
168}
169
170fn uint_id_digest(id: &PointId) -> Result<[u8; 32], Box<dyn std::error::Error>> {
171 let PointId::UInt(value) = id else {
172 return Err("sample-stable profile expects generated unsigned IDs".into());
173 };
174 let mut bytes = [0_u8; 10];
175 bytes[..2].copy_from_slice(b"u\0");
176 bytes[2..].copy_from_slice(&value.to_be_bytes());
177 Ok(Sha256::digest(bytes).into())
178}
179
180fn validate(
181 repository: &Path,
182 build_report: &Path,
183 output: &Path,
184) -> Result<(), Box<dyn std::error::Error>> {
185 let build: serde_json::Value = serde_json::from_slice(&fs::read(build_report)?)?;
186 let root = build
187 .get("root")
188 .and_then(serde_json::Value::as_str)
189 .ok_or("build report root is missing")?;
190 let engine = SnapshotEngine::open(repository)?;
191 let started = Instant::now();
192 let report = engine.validate(root, true)?;
193 fs::write(
194 output,
195 serde_json::to_vec_pretty(&json!({
196 "schema_version": 1,
197 "root": root,
198 "validation_us": micros(started),
199 "report": report,
200 }))?,
201 )?;
202 Ok(())
203}
204
205fn build(input: &Path, repository: &Path, output: &Path) -> Result<(), Box<dyn std::error::Error>> {
206 if repository.exists() {
207 return Err(format!("repository already exists: {}", repository.display()).into());
208 }
209 let spec = read_spec(input)?;
210 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
211 let points = make_points(&vectors);
212 let config = CollectionConfig {
213 dimension: spec.dimension,
214 ..CollectionConfig::default()
215 };
216 let engine = SnapshotEngine::init(repository)?;
217 let started = Instant::now();
218 let snapshot = engine.build(config, points)?;
219 let build_us = micros(started);
220 fs::write(
221 output,
222 serde_json::to_vec_pretty(&json!({
223 "schema_version": 1,
224 "root": snapshot.root(),
225 "build_us": build_us,
226 "on_disk_bytes": directory_bytes(repository)?,
227 }))?,
228 )?;
229 Ok(())
230}
231
232fn query(
233 input: &Path,
234 repository: &Path,
235 build_report: &Path,
236 mode: &str,
237 output: &Path,
238) -> Result<(), Box<dyn std::error::Error>> {
239 let (exact, warm_exact) = match mode {
240 "exact" => (true, false),
241 "approximate" => (false, false),
242 "approximate-after-exact" => (false, true),
243 _ => return Err(format!("unsupported query mode: {mode}").into()),
244 };
245 let spec = read_spec(input)?;
246 let queries = read_vectors(&spec.queries_path, spec.query_count, spec.dimension)?;
247 let maximum_k = *spec.k.iter().max().ok_or("k must not be empty")?;
248 let build: serde_json::Value = serde_json::from_slice(&fs::read(build_report)?)?;
249 let root = build
250 .get("root")
251 .and_then(serde_json::Value::as_str)
252 .ok_or("build report root is missing")?;
253 let engine = SnapshotEngine::open(repository)?;
254 let snapshot = engine.open_snapshot(root)?;
255
256 let cache_build_us = if warm_exact {
257 let started = Instant::now();
258 snapshot.query(make_query(&queries[0], maximum_k, true))?;
259 Some(micros(started))
260 } else {
261 None
262 };
263 // Fill the immutable snapshot cache, construct an approximate lookup over an
264 // already-warm exact view, or warm the unchanged approximate ODB path without
265 // including that one-time work in the samples.
266 let warmup_started = Instant::now();
267 snapshot.query(make_query(&queries[0], maximum_k, exact))?;
268 let warmup_us = micros(warmup_started);
269 wait_for_profiler()?;
270
271 let mut query_us = Vec::with_capacity(queries.len());
272 let mut results = Vec::with_capacity(queries.len());
273 let mut vectors_scored = Vec::with_capacity(queries.len());
274 let batch_started = Instant::now();
275 for vector in &queries {
276 let started = Instant::now();
277 let result = snapshot.query(make_query(vector, maximum_k, exact))?;
278 query_us.push(micros(started));
279 vectors_scored.push(result.stats.vectors_scored);
280 results.push(result.points);
281 }
282 let batch_us = micros(batch_started);
283 fs::write(
284 output,
285 serde_json::to_vec_pretty(&json!({
286 "schema_version": 1,
287 "root": root,
288 "mode": mode,
289 "cache_build_us": cache_build_us,
290 "warmup_us": warmup_us,
291 "query_us": query_us,
292 "batch_us": batch_us,
293 "vectors_scored": vectors_scored,
294 "results": results,
295 }))?,
296 )?;
297 Ok(())
298}Sourcepub fn init(path: impl AsRef<Path>) -> Result<Self>
pub fn init(path: impl AsRef<Path>) -> Result<Self>
Initializes a bare Git object database for immutable snapshots.
Examples found in repository?
205fn build(input: &Path, repository: &Path, output: &Path) -> Result<(), Box<dyn std::error::Error>> {
206 if repository.exists() {
207 return Err(format!("repository already exists: {}", repository.display()).into());
208 }
209 let spec = read_spec(input)?;
210 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
211 let points = make_points(&vectors);
212 let config = CollectionConfig {
213 dimension: spec.dimension,
214 ..CollectionConfig::default()
215 };
216 let engine = SnapshotEngine::init(repository)?;
217 let started = Instant::now();
218 let snapshot = engine.build(config, points)?;
219 let build_us = micros(started);
220 fs::write(
221 output,
222 serde_json::to_vec_pretty(&json!({
223 "schema_version": 1,
224 "root": snapshot.root(),
225 "build_us": build_us,
226 "on_disk_bytes": directory_bytes(repository)?,
227 }))?,
228 )?;
229 Ok(())
230}More examples
28fn main() -> Result<(), Box<dyn std::error::Error>> {
29 let mut args = env::args_os().skip(1);
30 let input = args.next().ok_or("usage: runner INPUT.json OUTPUT.json")?;
31 let output = args.next().ok_or("usage: runner INPUT.json OUTPUT.json")?;
32 if args.next().is_some() {
33 return Err("usage: runner INPUT.json OUTPUT.json".into());
34 }
35 let spec: RunSpec = serde_json::from_slice(&fs::read(input)?)?;
36 if spec.schema_version != 1 {
37 return Err(format!("unsupported harness schema version {}", spec.schema_version).into());
38 }
39 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
40 let queries = read_vectors(&spec.queries_path, spec.query_count, spec.dimension)?;
41 let points = make_points(&vectors);
42 let maximum_k = *spec.k.iter().max().ok_or("k must not be empty")?;
43 let config = CollectionConfig {
44 dimension: spec.dimension,
45 ..CollectionConfig::default()
46 };
47
48 let setup_started = Instant::now();
49 let core_dir = tempfile::TempDir::new()?;
50 let engine = SnapshotEngine::init(core_dir.path())?;
51 let setup_us = micros(setup_started);
52 let build_started = Instant::now();
53 let snapshot = engine.build(config.clone(), points.clone())?;
54 let build_us = micros(build_started);
55 let baseline_on_disk_bytes = directory_bytes(core_dir.path())?;
56
57 let (exact_query_us, exact_results, exact_vectors_scored) =
58 query_all(&snapshot, &queries, maximum_k, true, None)?;
59 let (approximate_query_us, approximate_results, approximate_vectors_scored) =
60 query_all(&snapshot, &queries, maximum_k, false, None)?;
61 let snapshot_throughput = json!({
62 "exact": query_snapshot_throughput(
63 &snapshot,
64 &queries,
65 maximum_k,
66 true,
67 &spec.concurrency,
68 )?,
69 "approximate": query_snapshot_throughput(
70 &snapshot,
71 &queries,
72 maximum_k,
73 false,
74 &spec.concurrency,
75 )?,
76 });
77
78 let mut filtered = Map::new();
79 for selectivity in &spec.filter_selectivities {
80 let filter = selectivity_filter(*selectivity);
81 let (exact_us, exact, exact_scored) =
82 query_all(&snapshot, &queries, maximum_k, true, Some(filter.clone()))?;
83 let (approximate_us, approximate, approximate_scored) =
84 query_all(&snapshot, &queries, maximum_k, false, Some(filter))?;
85 filtered.insert(
86 selectivity.to_string(),
87 json!({
88 "exact_query_us": exact_us,
89 "approximate_query_us": approximate_us,
90 "exact_results": exact,
91 "approximate_results": approximate,
92 "exact_vectors_scored": exact_scored,
93 "approximate_vectors_scored": approximate_scored,
94 }),
95 );
96 }
97
98 let mut mutations = Map::new();
99 for fraction in &spec.mutation_fractions {
100 let count = fraction_count(spec.point_count, *fraction);
101 let changed = changed_points(&points[..count]);
102 let upsert_started = Instant::now();
103 let upserted = engine.apply(
104 snapshot.root(),
105 changed.into_iter().map(SnapshotMutation::upsert).collect(),
106 )?;
107 let upsert_us = micros(upsert_started);
108 let delete_started = Instant::now();
109 let deleted = engine.apply(
110 snapshot.root(),
111 vec![SnapshotMutation::delete_ids(
112 (0..count).map(|id| PointId::from(id as u64)),
113 )],
114 )?;
115 let delete_us = micros(delete_started);
116 mutations.insert(
117 fraction.to_string(),
118 json!({
119 "points": count,
120 "upsert_us": upsert_us,
121 "delete_us": delete_us,
122 "upsert_root": upserted.root(),
123 "delete_root": deleted.root(),
124 }),
125 );
126 }
127
128 let adapter_setup_started = Instant::now();
129 let adapter_dir = tempfile::TempDir::new()?;
130 let database = Database::init_bare(adapter_dir.path())?;
131 let collection = database.create_collection("benchmark", config)?;
132 let adapter_setup_us = micros(adapter_setup_started);
133 let adapter_build_started = Instant::now();
134 let adapter_root = collection.upsert(points)?.root;
135 let adapter_build_us = micros(adapter_build_started);
136 if adapter_root != snapshot.root() {
137 return Err("snapshot-core and named-adapter roots differ".into());
138 }
139 query_collection_all(&collection, &queries[..1], maximum_k, true, None)?;
140 query_collection_all(&collection, &queries[..1], maximum_k, false, None)?;
141 let (adapter_exact_query_us, adapter_exact_results, adapter_exact_vectors_scored) =
142 query_collection_all(&collection, &queries, maximum_k, true, None)?;
143 let (
144 adapter_approximate_query_us,
145 adapter_approximate_results,
146 adapter_approximate_vectors_scored,
147 ) = query_collection_all(&collection, &queries, maximum_k, false, None)?;
148 let adapter_throughput = json!({
149 "exact": query_collection_throughput(
150 &collection,
151 &queries,
152 maximum_k,
153 true,
154 &spec.concurrency,
155 )?,
156 "approximate": query_collection_throughput(
157 &collection,
158 &queries,
159 maximum_k,
160 false,
161 &spec.concurrency,
162 )?,
163 });
164 let historical_started = Instant::now();
165 let historical = collection.at(&adapter_root)?;
166 let historical_count = historical.count(None)?.count;
167 let historical_read_us = micros(historical_started);
168
169 let report = json!({
170 "schema_version": 1,
171 "engine": "git-vdb",
172 "case_name": spec.case_name,
173 "point_count": spec.point_count,
174 "dimension": spec.dimension,
175 "query_count": spec.query_count,
176 "k": spec.k,
177 "root": snapshot.root(),
178 "setup_us": setup_us,
179 "snapshot_core": {
180 "build_us": build_us,
181 "exact_query_us": exact_query_us,
182 "approximate_query_us": approximate_query_us,
183 "exact_results": exact_results,
184 "approximate_results": approximate_results,
185 "exact_vectors_scored": exact_vectors_scored,
186 "approximate_vectors_scored": approximate_vectors_scored,
187 "throughput": snapshot_throughput,
188 "filtered": filtered,
189 "mutations": mutations,
190 "on_disk_bytes": baseline_on_disk_bytes,
191 },
192 "named_adapter": {
193 "setup_us": adapter_setup_us,
194 "build_us": adapter_build_us,
195 "exact_query_us": adapter_exact_query_us,
196 "approximate_query_us": adapter_approximate_query_us,
197 "exact_results": adapter_exact_results,
198 "approximate_results": adapter_approximate_results,
199 "exact_vectors_scored": adapter_exact_vectors_scored,
200 "approximate_vectors_scored": adapter_approximate_vectors_scored,
201 "throughput": adapter_throughput,
202 "historical_read_us": historical_read_us,
203 "historical_count": historical_count,
204 "on_disk_bytes": directory_bytes(adapter_dir.path())?,
205 }
206 });
207 fs::write(output, serde_json::to_vec_pretty(&report)?)?;
208 Ok(())
209}Sourcepub fn ephemeral() -> Result<Self>
pub fn ephemeral() -> Result<Self>
Creates an isolated temporary object database whose lifetime is retained by the engine and snapshots returned from it.
Sourcepub fn build(
&self,
config: CollectionConfig,
points: Vec<Point>,
) -> Result<Snapshot>
pub fn build( &self, config: CollectionConfig, points: Vec<Point>, ) -> Result<Snapshot>
Builds a canonical root from a complete point set.
Examples found in repository?
205fn build(input: &Path, repository: &Path, output: &Path) -> Result<(), Box<dyn std::error::Error>> {
206 if repository.exists() {
207 return Err(format!("repository already exists: {}", repository.display()).into());
208 }
209 let spec = read_spec(input)?;
210 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
211 let points = make_points(&vectors);
212 let config = CollectionConfig {
213 dimension: spec.dimension,
214 ..CollectionConfig::default()
215 };
216 let engine = SnapshotEngine::init(repository)?;
217 let started = Instant::now();
218 let snapshot = engine.build(config, points)?;
219 let build_us = micros(started);
220 fs::write(
221 output,
222 serde_json::to_vec_pretty(&json!({
223 "schema_version": 1,
224 "root": snapshot.root(),
225 "build_us": build_us,
226 "on_disk_bytes": directory_bytes(repository)?,
227 }))?,
228 )?;
229 Ok(())
230}More examples
28fn main() -> Result<(), Box<dyn std::error::Error>> {
29 let mut args = env::args_os().skip(1);
30 let input = args.next().ok_or("usage: runner INPUT.json OUTPUT.json")?;
31 let output = args.next().ok_or("usage: runner INPUT.json OUTPUT.json")?;
32 if args.next().is_some() {
33 return Err("usage: runner INPUT.json OUTPUT.json".into());
34 }
35 let spec: RunSpec = serde_json::from_slice(&fs::read(input)?)?;
36 if spec.schema_version != 1 {
37 return Err(format!("unsupported harness schema version {}", spec.schema_version).into());
38 }
39 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
40 let queries = read_vectors(&spec.queries_path, spec.query_count, spec.dimension)?;
41 let points = make_points(&vectors);
42 let maximum_k = *spec.k.iter().max().ok_or("k must not be empty")?;
43 let config = CollectionConfig {
44 dimension: spec.dimension,
45 ..CollectionConfig::default()
46 };
47
48 let setup_started = Instant::now();
49 let core_dir = tempfile::TempDir::new()?;
50 let engine = SnapshotEngine::init(core_dir.path())?;
51 let setup_us = micros(setup_started);
52 let build_started = Instant::now();
53 let snapshot = engine.build(config.clone(), points.clone())?;
54 let build_us = micros(build_started);
55 let baseline_on_disk_bytes = directory_bytes(core_dir.path())?;
56
57 let (exact_query_us, exact_results, exact_vectors_scored) =
58 query_all(&snapshot, &queries, maximum_k, true, None)?;
59 let (approximate_query_us, approximate_results, approximate_vectors_scored) =
60 query_all(&snapshot, &queries, maximum_k, false, None)?;
61 let snapshot_throughput = json!({
62 "exact": query_snapshot_throughput(
63 &snapshot,
64 &queries,
65 maximum_k,
66 true,
67 &spec.concurrency,
68 )?,
69 "approximate": query_snapshot_throughput(
70 &snapshot,
71 &queries,
72 maximum_k,
73 false,
74 &spec.concurrency,
75 )?,
76 });
77
78 let mut filtered = Map::new();
79 for selectivity in &spec.filter_selectivities {
80 let filter = selectivity_filter(*selectivity);
81 let (exact_us, exact, exact_scored) =
82 query_all(&snapshot, &queries, maximum_k, true, Some(filter.clone()))?;
83 let (approximate_us, approximate, approximate_scored) =
84 query_all(&snapshot, &queries, maximum_k, false, Some(filter))?;
85 filtered.insert(
86 selectivity.to_string(),
87 json!({
88 "exact_query_us": exact_us,
89 "approximate_query_us": approximate_us,
90 "exact_results": exact,
91 "approximate_results": approximate,
92 "exact_vectors_scored": exact_scored,
93 "approximate_vectors_scored": approximate_scored,
94 }),
95 );
96 }
97
98 let mut mutations = Map::new();
99 for fraction in &spec.mutation_fractions {
100 let count = fraction_count(spec.point_count, *fraction);
101 let changed = changed_points(&points[..count]);
102 let upsert_started = Instant::now();
103 let upserted = engine.apply(
104 snapshot.root(),
105 changed.into_iter().map(SnapshotMutation::upsert).collect(),
106 )?;
107 let upsert_us = micros(upsert_started);
108 let delete_started = Instant::now();
109 let deleted = engine.apply(
110 snapshot.root(),
111 vec![SnapshotMutation::delete_ids(
112 (0..count).map(|id| PointId::from(id as u64)),
113 )],
114 )?;
115 let delete_us = micros(delete_started);
116 mutations.insert(
117 fraction.to_string(),
118 json!({
119 "points": count,
120 "upsert_us": upsert_us,
121 "delete_us": delete_us,
122 "upsert_root": upserted.root(),
123 "delete_root": deleted.root(),
124 }),
125 );
126 }
127
128 let adapter_setup_started = Instant::now();
129 let adapter_dir = tempfile::TempDir::new()?;
130 let database = Database::init_bare(adapter_dir.path())?;
131 let collection = database.create_collection("benchmark", config)?;
132 let adapter_setup_us = micros(adapter_setup_started);
133 let adapter_build_started = Instant::now();
134 let adapter_root = collection.upsert(points)?.root;
135 let adapter_build_us = micros(adapter_build_started);
136 if adapter_root != snapshot.root() {
137 return Err("snapshot-core and named-adapter roots differ".into());
138 }
139 query_collection_all(&collection, &queries[..1], maximum_k, true, None)?;
140 query_collection_all(&collection, &queries[..1], maximum_k, false, None)?;
141 let (adapter_exact_query_us, adapter_exact_results, adapter_exact_vectors_scored) =
142 query_collection_all(&collection, &queries, maximum_k, true, None)?;
143 let (
144 adapter_approximate_query_us,
145 adapter_approximate_results,
146 adapter_approximate_vectors_scored,
147 ) = query_collection_all(&collection, &queries, maximum_k, false, None)?;
148 let adapter_throughput = json!({
149 "exact": query_collection_throughput(
150 &collection,
151 &queries,
152 maximum_k,
153 true,
154 &spec.concurrency,
155 )?,
156 "approximate": query_collection_throughput(
157 &collection,
158 &queries,
159 maximum_k,
160 false,
161 &spec.concurrency,
162 )?,
163 });
164 let historical_started = Instant::now();
165 let historical = collection.at(&adapter_root)?;
166 let historical_count = historical.count(None)?.count;
167 let historical_read_us = micros(historical_started);
168
169 let report = json!({
170 "schema_version": 1,
171 "engine": "git-vdb",
172 "case_name": spec.case_name,
173 "point_count": spec.point_count,
174 "dimension": spec.dimension,
175 "query_count": spec.query_count,
176 "k": spec.k,
177 "root": snapshot.root(),
178 "setup_us": setup_us,
179 "snapshot_core": {
180 "build_us": build_us,
181 "exact_query_us": exact_query_us,
182 "approximate_query_us": approximate_query_us,
183 "exact_results": exact_results,
184 "approximate_results": approximate_results,
185 "exact_vectors_scored": exact_vectors_scored,
186 "approximate_vectors_scored": approximate_vectors_scored,
187 "throughput": snapshot_throughput,
188 "filtered": filtered,
189 "mutations": mutations,
190 "on_disk_bytes": baseline_on_disk_bytes,
191 },
192 "named_adapter": {
193 "setup_us": adapter_setup_us,
194 "build_us": adapter_build_us,
195 "exact_query_us": adapter_exact_query_us,
196 "approximate_query_us": adapter_approximate_query_us,
197 "exact_results": adapter_exact_results,
198 "approximate_results": adapter_approximate_results,
199 "exact_vectors_scored": adapter_exact_vectors_scored,
200 "approximate_vectors_scored": adapter_approximate_vectors_scored,
201 "throughput": adapter_throughput,
202 "historical_read_us": historical_read_us,
203 "historical_count": historical_count,
204 "on_disk_bytes": directory_bytes(adapter_dir.path())?,
205 }
206 });
207 fs::write(output, serde_json::to_vec_pretty(&report)?)?;
208 Ok(())
209}Sourcepub fn apply(
&self,
previous_root: impl AsRef<str>,
mutations: Vec<SnapshotMutation>,
) -> Result<Snapshot>
pub fn apply( &self, previous_root: impl AsRef<str>, mutations: Vec<SnapshotMutation>, ) -> Result<Snapshot>
Applies an ordered mutation batch to a root and returns the new root.
Mutation order is significant. Duplicate upserts for the same typed ID within one call are rejected. No ref or commit is created.
Examples found in repository?
79fn mutate(
80 input: &Path,
81 repository: &Path,
82 build_report: &Path,
83 fraction: f64,
84 output: &Path,
85 sample_stable: bool,
86) -> Result<(), Box<dyn std::error::Error>> {
87 let spec = read_spec(input)?;
88 if !(0.0..=1.0).contains(&fraction) || fraction == 0.0 {
89 return Err("mutation fraction must be greater than zero and at most one".into());
90 }
91 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
92 let points = make_points(&vectors);
93 let count = ((spec.point_count as f64 * fraction).round() as usize).clamp(1, spec.point_count);
94 let mut changed = if sample_stable {
95 sample_stable_points(points, count)?
96 } else {
97 points[..count].to_vec()
98 };
99 for point in &mut changed {
100 point.vector[0] += 0.001;
101 }
102 let build: serde_json::Value = serde_json::from_slice(&fs::read(build_report)?)?;
103 let root = build
104 .get("root")
105 .and_then(serde_json::Value::as_str)
106 .ok_or("build report root is missing")?;
107 let engine = SnapshotEngine::open(repository)?;
108
109 let upsert_started = Instant::now();
110 let upserted = engine.apply(
111 root,
112 changed.into_iter().map(SnapshotMutation::upsert).collect(),
113 )?;
114 let upsert_us = micros(upsert_started);
115 let delete_started = Instant::now();
116 let deleted = engine.apply(
117 root,
118 vec![SnapshotMutation::delete_ids(
119 (0..count).map(|id| PointId::from(id as u64)),
120 )],
121 )?;
122 let delete_us = micros(delete_started);
123 fs::write(
124 output,
125 serde_json::to_vec_pretty(&json!({
126 "schema_version": 1,
127 "root": root,
128 "fraction": fraction,
129 "points": count,
130 "sample_stable": sample_stable,
131 "upsert_us": upsert_us,
132 "delete_us": delete_us,
133 "upsert_root": upserted.root(),
134 "delete_root": deleted.root(),
135 "on_disk_bytes_after": directory_bytes(repository)?,
136 }))?,
137 )?;
138 Ok(())
139}More examples
28fn main() -> Result<(), Box<dyn std::error::Error>> {
29 let mut args = env::args_os().skip(1);
30 let input = args.next().ok_or("usage: runner INPUT.json OUTPUT.json")?;
31 let output = args.next().ok_or("usage: runner INPUT.json OUTPUT.json")?;
32 if args.next().is_some() {
33 return Err("usage: runner INPUT.json OUTPUT.json".into());
34 }
35 let spec: RunSpec = serde_json::from_slice(&fs::read(input)?)?;
36 if spec.schema_version != 1 {
37 return Err(format!("unsupported harness schema version {}", spec.schema_version).into());
38 }
39 let vectors = read_vectors(&spec.points_path, spec.point_count, spec.dimension)?;
40 let queries = read_vectors(&spec.queries_path, spec.query_count, spec.dimension)?;
41 let points = make_points(&vectors);
42 let maximum_k = *spec.k.iter().max().ok_or("k must not be empty")?;
43 let config = CollectionConfig {
44 dimension: spec.dimension,
45 ..CollectionConfig::default()
46 };
47
48 let setup_started = Instant::now();
49 let core_dir = tempfile::TempDir::new()?;
50 let engine = SnapshotEngine::init(core_dir.path())?;
51 let setup_us = micros(setup_started);
52 let build_started = Instant::now();
53 let snapshot = engine.build(config.clone(), points.clone())?;
54 let build_us = micros(build_started);
55 let baseline_on_disk_bytes = directory_bytes(core_dir.path())?;
56
57 let (exact_query_us, exact_results, exact_vectors_scored) =
58 query_all(&snapshot, &queries, maximum_k, true, None)?;
59 let (approximate_query_us, approximate_results, approximate_vectors_scored) =
60 query_all(&snapshot, &queries, maximum_k, false, None)?;
61 let snapshot_throughput = json!({
62 "exact": query_snapshot_throughput(
63 &snapshot,
64 &queries,
65 maximum_k,
66 true,
67 &spec.concurrency,
68 )?,
69 "approximate": query_snapshot_throughput(
70 &snapshot,
71 &queries,
72 maximum_k,
73 false,
74 &spec.concurrency,
75 )?,
76 });
77
78 let mut filtered = Map::new();
79 for selectivity in &spec.filter_selectivities {
80 let filter = selectivity_filter(*selectivity);
81 let (exact_us, exact, exact_scored) =
82 query_all(&snapshot, &queries, maximum_k, true, Some(filter.clone()))?;
83 let (approximate_us, approximate, approximate_scored) =
84 query_all(&snapshot, &queries, maximum_k, false, Some(filter))?;
85 filtered.insert(
86 selectivity.to_string(),
87 json!({
88 "exact_query_us": exact_us,
89 "approximate_query_us": approximate_us,
90 "exact_results": exact,
91 "approximate_results": approximate,
92 "exact_vectors_scored": exact_scored,
93 "approximate_vectors_scored": approximate_scored,
94 }),
95 );
96 }
97
98 let mut mutations = Map::new();
99 for fraction in &spec.mutation_fractions {
100 let count = fraction_count(spec.point_count, *fraction);
101 let changed = changed_points(&points[..count]);
102 let upsert_started = Instant::now();
103 let upserted = engine.apply(
104 snapshot.root(),
105 changed.into_iter().map(SnapshotMutation::upsert).collect(),
106 )?;
107 let upsert_us = micros(upsert_started);
108 let delete_started = Instant::now();
109 let deleted = engine.apply(
110 snapshot.root(),
111 vec![SnapshotMutation::delete_ids(
112 (0..count).map(|id| PointId::from(id as u64)),
113 )],
114 )?;
115 let delete_us = micros(delete_started);
116 mutations.insert(
117 fraction.to_string(),
118 json!({
119 "points": count,
120 "upsert_us": upsert_us,
121 "delete_us": delete_us,
122 "upsert_root": upserted.root(),
123 "delete_root": deleted.root(),
124 }),
125 );
126 }
127
128 let adapter_setup_started = Instant::now();
129 let adapter_dir = tempfile::TempDir::new()?;
130 let database = Database::init_bare(adapter_dir.path())?;
131 let collection = database.create_collection("benchmark", config)?;
132 let adapter_setup_us = micros(adapter_setup_started);
133 let adapter_build_started = Instant::now();
134 let adapter_root = collection.upsert(points)?.root;
135 let adapter_build_us = micros(adapter_build_started);
136 if adapter_root != snapshot.root() {
137 return Err("snapshot-core and named-adapter roots differ".into());
138 }
139 query_collection_all(&collection, &queries[..1], maximum_k, true, None)?;
140 query_collection_all(&collection, &queries[..1], maximum_k, false, None)?;
141 let (adapter_exact_query_us, adapter_exact_results, adapter_exact_vectors_scored) =
142 query_collection_all(&collection, &queries, maximum_k, true, None)?;
143 let (
144 adapter_approximate_query_us,
145 adapter_approximate_results,
146 adapter_approximate_vectors_scored,
147 ) = query_collection_all(&collection, &queries, maximum_k, false, None)?;
148 let adapter_throughput = json!({
149 "exact": query_collection_throughput(
150 &collection,
151 &queries,
152 maximum_k,
153 true,
154 &spec.concurrency,
155 )?,
156 "approximate": query_collection_throughput(
157 &collection,
158 &queries,
159 maximum_k,
160 false,
161 &spec.concurrency,
162 )?,
163 });
164 let historical_started = Instant::now();
165 let historical = collection.at(&adapter_root)?;
166 let historical_count = historical.count(None)?.count;
167 let historical_read_us = micros(historical_started);
168
169 let report = json!({
170 "schema_version": 1,
171 "engine": "git-vdb",
172 "case_name": spec.case_name,
173 "point_count": spec.point_count,
174 "dimension": spec.dimension,
175 "query_count": spec.query_count,
176 "k": spec.k,
177 "root": snapshot.root(),
178 "setup_us": setup_us,
179 "snapshot_core": {
180 "build_us": build_us,
181 "exact_query_us": exact_query_us,
182 "approximate_query_us": approximate_query_us,
183 "exact_results": exact_results,
184 "approximate_results": approximate_results,
185 "exact_vectors_scored": exact_vectors_scored,
186 "approximate_vectors_scored": approximate_vectors_scored,
187 "throughput": snapshot_throughput,
188 "filtered": filtered,
189 "mutations": mutations,
190 "on_disk_bytes": baseline_on_disk_bytes,
191 },
192 "named_adapter": {
193 "setup_us": adapter_setup_us,
194 "build_us": adapter_build_us,
195 "exact_query_us": adapter_exact_query_us,
196 "approximate_query_us": adapter_approximate_query_us,
197 "exact_results": adapter_exact_results,
198 "approximate_results": adapter_approximate_results,
199 "exact_vectors_scored": adapter_exact_vectors_scored,
200 "approximate_vectors_scored": adapter_approximate_vectors_scored,
201 "throughput": adapter_throughput,
202 "historical_read_us": historical_read_us,
203 "historical_count": historical_count,
204 "on_disk_bytes": directory_bytes(adapter_dir.path())?,
205 }
206 });
207 fs::write(output, serde_json::to_vec_pretty(&report)?)?;
208 Ok(())
209}Sourcepub fn open_snapshot(&self, root: impl AsRef<str>) -> Result<Snapshot>
pub fn open_snapshot(&self, root: impl AsRef<str>) -> Result<Snapshot>
Opens an exact tree object ID without resolving refs or commits.
Examples found in repository?
232fn query(
233 input: &Path,
234 repository: &Path,
235 build_report: &Path,
236 mode: &str,
237 output: &Path,
238) -> Result<(), Box<dyn std::error::Error>> {
239 let (exact, warm_exact) = match mode {
240 "exact" => (true, false),
241 "approximate" => (false, false),
242 "approximate-after-exact" => (false, true),
243 _ => return Err(format!("unsupported query mode: {mode}").into()),
244 };
245 let spec = read_spec(input)?;
246 let queries = read_vectors(&spec.queries_path, spec.query_count, spec.dimension)?;
247 let maximum_k = *spec.k.iter().max().ok_or("k must not be empty")?;
248 let build: serde_json::Value = serde_json::from_slice(&fs::read(build_report)?)?;
249 let root = build
250 .get("root")
251 .and_then(serde_json::Value::as_str)
252 .ok_or("build report root is missing")?;
253 let engine = SnapshotEngine::open(repository)?;
254 let snapshot = engine.open_snapshot(root)?;
255
256 let cache_build_us = if warm_exact {
257 let started = Instant::now();
258 snapshot.query(make_query(&queries[0], maximum_k, true))?;
259 Some(micros(started))
260 } else {
261 None
262 };
263 // Fill the immutable snapshot cache, construct an approximate lookup over an
264 // already-warm exact view, or warm the unchanged approximate ODB path without
265 // including that one-time work in the samples.
266 let warmup_started = Instant::now();
267 snapshot.query(make_query(&queries[0], maximum_k, exact))?;
268 let warmup_us = micros(warmup_started);
269 wait_for_profiler()?;
270
271 let mut query_us = Vec::with_capacity(queries.len());
272 let mut results = Vec::with_capacity(queries.len());
273 let mut vectors_scored = Vec::with_capacity(queries.len());
274 let batch_started = Instant::now();
275 for vector in &queries {
276 let started = Instant::now();
277 let result = snapshot.query(make_query(vector, maximum_k, exact))?;
278 query_us.push(micros(started));
279 vectors_scored.push(result.stats.vectors_scored);
280 results.push(result.points);
281 }
282 let batch_us = micros(batch_started);
283 fs::write(
284 output,
285 serde_json::to_vec_pretty(&json!({
286 "schema_version": 1,
287 "root": root,
288 "mode": mode,
289 "cache_build_us": cache_build_us,
290 "warmup_us": warmup_us,
291 "query_us": query_us,
292 "batch_us": batch_us,
293 "vectors_scored": vectors_scored,
294 "results": results,
295 }))?,
296 )?;
297 Ok(())
298}Sourcepub fn import_directory(&self, path: impl AsRef<Path>) -> Result<Snapshot>
pub fn import_directory(&self, path: impl AsRef<Path>) -> Result<Snapshot>
Imports a materialized canonical tree into this engine’s object database.
Sourcepub fn query(&self, root: impl AsRef<str>, query: Query) -> Result<QueryResult>
pub fn query(&self, root: impl AsRef<str>, query: Query) -> Result<QueryResult>
Queries an exact root ID without first constructing a named collection.
Sourcepub fn get(
&self,
root: impl AsRef<str>,
request: GetRequest,
) -> Result<GetResult>
pub fn get( &self, root: impl AsRef<str>, request: GetRequest, ) -> Result<GetResult>
Retrieves records from an exact root ID.
Sourcepub fn count(
&self,
root: impl AsRef<str>,
filter: Option<Filter>,
) -> Result<CountResult>
pub fn count( &self, root: impl AsRef<str>, filter: Option<Filter>, ) -> Result<CountResult>
Counts records in an exact root ID.
Sourcepub fn validate(
&self,
root: impl AsRef<str>,
full: bool,
) -> Result<ValidationReport>
pub fn validate( &self, root: impl AsRef<str>, full: bool, ) -> Result<ValidationReport>
Validates an exact root ID.
Examples found in repository?
180fn validate(
181 repository: &Path,
182 build_report: &Path,
183 output: &Path,
184) -> Result<(), Box<dyn std::error::Error>> {
185 let build: serde_json::Value = serde_json::from_slice(&fs::read(build_report)?)?;
186 let root = build
187 .get("root")
188 .and_then(serde_json::Value::as_str)
189 .ok_or("build report root is missing")?;
190 let engine = SnapshotEngine::open(repository)?;
191 let started = Instant::now();
192 let report = engine.validate(root, true)?;
193 fs::write(
194 output,
195 serde_json::to_vec_pretty(&json!({
196 "schema_version": 1,
197 "root": root,
198 "validation_us": micros(started),
199 "report": report,
200 }))?,
201 )?;
202 Ok(())
203}Sourcepub fn build_directory(
path: impl AsRef<Path>,
config: CollectionConfig,
points: Vec<Point>,
) -> Result<Snapshot>
pub fn build_directory( path: impl AsRef<Path>, config: CollectionConfig, points: Vec<Point>, ) -> Result<Snapshot>
Builds a snapshot without a caller-provided Git repository and writes its canonical files into a new materialized directory.
Trait Implementations§
Source§impl Clone for SnapshotEngine
impl Clone for SnapshotEngine
Auto Trait Implementations§
impl Freeze for SnapshotEngine
impl RefUnwindSafe for SnapshotEngine
impl Send for SnapshotEngine
impl Sync for SnapshotEngine
impl Unpin for SnapshotEngine
impl UnsafeUnpin for SnapshotEngine
impl UnwindSafe for SnapshotEngine
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more