pub struct Database { /* private fields */ }Expand description
An in-memory database backed by a snapshot and a checksummed .wal journal.
Mutations become durable at save(). Use checkpoint() before copying a
standalone file. Concurrent handles can read; stale writers fail on save.
Implementations§
Source§impl Database
impl Database
Sourcepub fn create(path: impl AsRef<Path>) -> Result<Self>
pub fn create(path: impl AsRef<Path>) -> Result<Self>
Examples found in repository?
examples/quickstart.rs (line 14)
9fn main() -> recern_vector::Result<()> {
10 let path = std::env::temp_dir().join("recern-vector-quickstart.rvec");
11 let _ = std::fs::remove_file(&path);
12 let _ = std::fs::remove_file(format!("{}.wal", path.display()));
13
14 let mut db = Database::create(&path)?;
15 let docs = db.create_collection("docs", CollectionConfig::new(32, Metric::Cosine))?;
16
17 // Batch insert: (id, vector, metadata). Built on all cores, atomic.
18 let records: Vec<_> = (0..10_000)
19 .map(|i| {
20 let vector: Vec<f32> = (0..32).map(|d| pseudo_random(i * 32 + d)).collect();
21 let lang = ["en", "de", "fr"][i % 3];
22 (
23 format!("doc-{i}"),
24 vector,
25 Some(json!({"lang": lang, "year": 2015 + i % 10})),
26 )
27 })
28 .collect();
29 let query = records[42].1.clone();
30 docs.upsert_many(records)?;
31 println!("{} records", docs.len());
32
33 // Search with a filter; explain() returns the hits and how they were found.
34 let filter = Filter::from_json(&json!({"lang": "en", "year": {"$gte": 2020}}))?;
35 // The same filter, built in code:
36 let _same = Filter::And(vec![
37 Filter::eq("lang", "en"),
38 Filter::between("year", Some(2020.0), None),
39 ]);
40 let report = docs.explain(&query, 3, &SearchOptions::default().filter(filter))?;
41 for hit in &report.hits {
42 println!(
43 " {:<10} distance={:.4} {}",
44 hit.id,
45 hit.distance,
46 hit.metadata.as_ref().unwrap()
47 );
48 }
49 println!(
50 "strategy={} visited={} selectivity={:.1}% time={:?}",
51 report.strategy,
52 report.visited,
53 report.filter_selectivity.unwrap_or(1.0) * 100.0,
54 report.elapsed
55 );
56
57 // Index health and recall against exact search.
58 let stats = docs.stats();
59 println!(
60 "layers={:?} unreachable={}",
61 stats.nodes_per_layer, stats.unreachable
62 );
63 let recall = docs.estimate_recall(&RecallOptions {
64 ef_values: vec![16, 64],
65 ..Default::default()
66 })?;
67 for point in &recall.points {
68 println!(
69 " ef={:<3} recall@{}={:.3}",
70 point.ef, recall.k, point.recall
71 );
72 }
73
74 db.save()?;
75 let reopened = Database::open(&path)?;
76 println!("reopened: {} records", reopened.collection("docs")?.len());
77 Ok(())
78}More examples
examples/bench.rs (line 70)
55fn main() -> recern_vector::Result<()> {
56 let args: Vec<String> = std::env::args().skip(1).collect();
57 let arg = |i: usize| {
58 args.get(i)
59 .map(|a| a.parse::<f64>().expect("numeric argument"))
60 };
61 let records = arg(0).map_or(50_000, |v| v as usize);
62 let dim = arg(1).map_or(128, |v| v as usize);
63 let noise = arg(2).map_or(1.0, |v| v as f32);
64 let path = std::env::temp_dir().join("recern-vector-bench.rvec");
65 let _ = std::fs::remove_file(&path);
66
67 let mut data = Clusters::new(100, dim, noise, 0x5EED);
68 let vectors: Vec<Vec<f32>> = (0..records).map(|_| data.sample()).collect();
69
70 let mut db = Database::create(&path)?;
71 let collection = db.create_collection("bench", CollectionConfig::new(dim, Metric::Cosine))?;
72 let start = Instant::now();
73 for (i, v) in vectors.iter().enumerate() {
74 collection.upsert(&i.to_string(), v, None)?;
75 }
76 let build = start.elapsed();
77
78 let start = Instant::now();
79 db.save()?;
80 let save = start.elapsed();
81 let start = Instant::now();
82 let db = Database::open(&path)?;
83 let open = start.elapsed();
84 let collection = db.collection("bench")?;
85 let stats = collection.stats();
86
87 println!("records {records} · dim {dim} · noise {noise} · cosine · m=16 ef_construction=200");
88 println!(
89 "build {:.2} s ({:.0} inserts/s)",
90 build.as_secs_f64(),
91 records as f64 / build.as_secs_f64()
92 );
93 println!(
94 "file {:.1} MB · save {:.0} ms · open {:.0} ms",
95 std::fs::metadata(&path)?.len() as f64 / 1_048_576.0,
96 save.as_secs_f64() * 1e3,
97 open.as_secs_f64() * 1e3
98 );
99 println!(
100 "graph layers {:?} · avg degree L0 {:.1} · unreachable {}",
101 stats.nodes_per_layer, stats.avg_degree_layer0, stats.unreachable
102 );
103
104 let report = collection.estimate_recall(&RecallOptions {
105 sample: 200,
106 k: 10,
107 ef_values: vec![10, 16, 32, 64, 128, 256],
108 seed: 7,
109 })?;
110 println!();
111 println!(
112 "exact scan p50 {:.3} ms",
113 report.exact_p50.as_secs_f64() * 1e3
114 );
115 println!(
116 "{:>6} {:>9} {:>10} {:>10} {:>9}",
117 "ef", "recall@10", "p50 ms", "p95 ms", "speedup"
118 );
119 for p in &report.points {
120 println!(
121 "{:>6} {:>9.3} {:>10.3} {:>10.3} {:>8.0}x",
122 p.ef,
123 p.recall,
124 p.p50.as_secs_f64() * 1e3,
125 p.p95.as_secs_f64() * 1e3,
126 report.exact_p50.as_secs_f64() / p.p50.as_secs_f64()
127 );
128 }
129
130 let _ = std::fs::remove_file(&path);
131 Ok(())
132}Sourcepub fn open(path: impl AsRef<Path>) -> Result<Self>
pub fn open(path: impl AsRef<Path>) -> Result<Self>
Examples found in repository?
examples/search_bench.rs (line 25)
23fn main() {
24 let args: Vec<String> = std::env::args().skip(1).collect();
25 let db = Database::open(&args[0]).expect("index");
26 let c = db.collections().next().expect("a collection");
27 let dim = c.config().dim;
28 let queries = read(&args[1], f32::from_le_bytes);
29 let truth = read(&args[2], u32::from_le_bytes);
30 let efs: Vec<usize> = args
31 .get(3)
32 .map_or("10,20,40,80,160", String::as_str)
33 .split(',')
34 .map(|e| e.parse().unwrap())
35 .collect();
36 let n = queries.len() / dim;
37
38 println!(
39 "{:>5} {:>8} {:>9} {:>9} {:>10}",
40 "ef", "recall", "mean µs", "p50 µs", "distances"
41 );
42 for ef in efs {
43 let options = SearchOptions::default().ef(ef);
44 let (mut times, mut found, mut distances) = (Vec::with_capacity(n), 0, 0);
45 for (q, t) in queries
46 .chunks_exact(dim)
47 .zip(truth.as_chunks::<10>().0.iter())
48 {
49 let start = Instant::now();
50 let report = c.explain(q, 10, &options).unwrap();
51 times.push(start.elapsed().as_secs_f64() * 1e6);
52 distances += report.distance_computations;
53 found += report
54 .hits
55 .iter()
56 .filter(|h| t.contains(&h.id.parse::<u32>().unwrap()))
57 .count();
58 }
59 let mean = times.iter().sum::<f64>() / n as f64;
60 times.sort_by(f64::total_cmp);
61 println!(
62 "{ef:>5} {:>8.4} {mean:>9.1} {:>9.1} {:>10}",
63 found as f64 / (n * 10) as f64,
64 times[n / 2],
65 distances / n
66 );
67 }
68}More examples
examples/quickstart.rs (line 75)
9fn main() -> recern_vector::Result<()> {
10 let path = std::env::temp_dir().join("recern-vector-quickstart.rvec");
11 let _ = std::fs::remove_file(&path);
12 let _ = std::fs::remove_file(format!("{}.wal", path.display()));
13
14 let mut db = Database::create(&path)?;
15 let docs = db.create_collection("docs", CollectionConfig::new(32, Metric::Cosine))?;
16
17 // Batch insert: (id, vector, metadata). Built on all cores, atomic.
18 let records: Vec<_> = (0..10_000)
19 .map(|i| {
20 let vector: Vec<f32> = (0..32).map(|d| pseudo_random(i * 32 + d)).collect();
21 let lang = ["en", "de", "fr"][i % 3];
22 (
23 format!("doc-{i}"),
24 vector,
25 Some(json!({"lang": lang, "year": 2015 + i % 10})),
26 )
27 })
28 .collect();
29 let query = records[42].1.clone();
30 docs.upsert_many(records)?;
31 println!("{} records", docs.len());
32
33 // Search with a filter; explain() returns the hits and how they were found.
34 let filter = Filter::from_json(&json!({"lang": "en", "year": {"$gte": 2020}}))?;
35 // The same filter, built in code:
36 let _same = Filter::And(vec![
37 Filter::eq("lang", "en"),
38 Filter::between("year", Some(2020.0), None),
39 ]);
40 let report = docs.explain(&query, 3, &SearchOptions::default().filter(filter))?;
41 for hit in &report.hits {
42 println!(
43 " {:<10} distance={:.4} {}",
44 hit.id,
45 hit.distance,
46 hit.metadata.as_ref().unwrap()
47 );
48 }
49 println!(
50 "strategy={} visited={} selectivity={:.1}% time={:?}",
51 report.strategy,
52 report.visited,
53 report.filter_selectivity.unwrap_or(1.0) * 100.0,
54 report.elapsed
55 );
56
57 // Index health and recall against exact search.
58 let stats = docs.stats();
59 println!(
60 "layers={:?} unreachable={}",
61 stats.nodes_per_layer, stats.unreachable
62 );
63 let recall = docs.estimate_recall(&RecallOptions {
64 ef_values: vec![16, 64],
65 ..Default::default()
66 })?;
67 for point in &recall.points {
68 println!(
69 " ef={:<3} recall@{}={:.3}",
70 point.ef, recall.k, point.recall
71 );
72 }
73
74 db.save()?;
75 let reopened = Database::open(&path)?;
76 println!("reopened: {} records", reopened.collection("docs")?.len());
77 Ok(())
78}examples/bench.rs (line 82)
55fn main() -> recern_vector::Result<()> {
56 let args: Vec<String> = std::env::args().skip(1).collect();
57 let arg = |i: usize| {
58 args.get(i)
59 .map(|a| a.parse::<f64>().expect("numeric argument"))
60 };
61 let records = arg(0).map_or(50_000, |v| v as usize);
62 let dim = arg(1).map_or(128, |v| v as usize);
63 let noise = arg(2).map_or(1.0, |v| v as f32);
64 let path = std::env::temp_dir().join("recern-vector-bench.rvec");
65 let _ = std::fs::remove_file(&path);
66
67 let mut data = Clusters::new(100, dim, noise, 0x5EED);
68 let vectors: Vec<Vec<f32>> = (0..records).map(|_| data.sample()).collect();
69
70 let mut db = Database::create(&path)?;
71 let collection = db.create_collection("bench", CollectionConfig::new(dim, Metric::Cosine))?;
72 let start = Instant::now();
73 for (i, v) in vectors.iter().enumerate() {
74 collection.upsert(&i.to_string(), v, None)?;
75 }
76 let build = start.elapsed();
77
78 let start = Instant::now();
79 db.save()?;
80 let save = start.elapsed();
81 let start = Instant::now();
82 let db = Database::open(&path)?;
83 let open = start.elapsed();
84 let collection = db.collection("bench")?;
85 let stats = collection.stats();
86
87 println!("records {records} · dim {dim} · noise {noise} · cosine · m=16 ef_construction=200");
88 println!(
89 "build {:.2} s ({:.0} inserts/s)",
90 build.as_secs_f64(),
91 records as f64 / build.as_secs_f64()
92 );
93 println!(
94 "file {:.1} MB · save {:.0} ms · open {:.0} ms",
95 std::fs::metadata(&path)?.len() as f64 / 1_048_576.0,
96 save.as_secs_f64() * 1e3,
97 open.as_secs_f64() * 1e3
98 );
99 println!(
100 "graph layers {:?} · avg degree L0 {:.1} · unreachable {}",
101 stats.nodes_per_layer, stats.avg_degree_layer0, stats.unreachable
102 );
103
104 let report = collection.estimate_recall(&RecallOptions {
105 sample: 200,
106 k: 10,
107 ef_values: vec![10, 16, 32, 64, 128, 256],
108 seed: 7,
109 })?;
110 println!();
111 println!(
112 "exact scan p50 {:.3} ms",
113 report.exact_p50.as_secs_f64() * 1e3
114 );
115 println!(
116 "{:>6} {:>9} {:>10} {:>10} {:>9}",
117 "ef", "recall@10", "p50 ms", "p95 ms", "speedup"
118 );
119 for p in &report.points {
120 println!(
121 "{:>6} {:>9.3} {:>10.3} {:>10.3} {:>8.0}x",
122 p.ef,
123 p.recall,
124 p.p50.as_secs_f64() * 1e3,
125 p.p95.as_secs_f64() * 1e3,
126 report.exact_p50.as_secs_f64() / p.p50.as_secs_f64()
127 );
128 }
129
130 let _ = std::fs::remove_file(&path);
131 Ok(())
132}Sourcepub fn open_read_only(path: impl AsRef<Path>) -> Result<Self>
pub fn open_read_only(path: impl AsRef<Path>) -> Result<Self>
Loads a consistent snapshot including committed WAL records, without creating sidecars or allowing mutations through this handle.
pub fn open_or_create(path: impl AsRef<Path>) -> Result<Self>
Sourcepub fn format_version(&self) -> u32
pub fn format_version(&self) -> u32
Format of this handle’s last durable snapshot (including WAL).
pub fn is_read_only(&self) -> bool
pub fn path(&self) -> &Path
Sourcepub fn create_collection(
&mut self,
name: &str,
config: CollectionConfig,
) -> Result<&mut Collection>
pub fn create_collection( &mut self, name: &str, config: CollectionConfig, ) -> Result<&mut Collection>
Examples found in repository?
examples/quickstart.rs (line 15)
9fn main() -> recern_vector::Result<()> {
10 let path = std::env::temp_dir().join("recern-vector-quickstart.rvec");
11 let _ = std::fs::remove_file(&path);
12 let _ = std::fs::remove_file(format!("{}.wal", path.display()));
13
14 let mut db = Database::create(&path)?;
15 let docs = db.create_collection("docs", CollectionConfig::new(32, Metric::Cosine))?;
16
17 // Batch insert: (id, vector, metadata). Built on all cores, atomic.
18 let records: Vec<_> = (0..10_000)
19 .map(|i| {
20 let vector: Vec<f32> = (0..32).map(|d| pseudo_random(i * 32 + d)).collect();
21 let lang = ["en", "de", "fr"][i % 3];
22 (
23 format!("doc-{i}"),
24 vector,
25 Some(json!({"lang": lang, "year": 2015 + i % 10})),
26 )
27 })
28 .collect();
29 let query = records[42].1.clone();
30 docs.upsert_many(records)?;
31 println!("{} records", docs.len());
32
33 // Search with a filter; explain() returns the hits and how they were found.
34 let filter = Filter::from_json(&json!({"lang": "en", "year": {"$gte": 2020}}))?;
35 // The same filter, built in code:
36 let _same = Filter::And(vec![
37 Filter::eq("lang", "en"),
38 Filter::between("year", Some(2020.0), None),
39 ]);
40 let report = docs.explain(&query, 3, &SearchOptions::default().filter(filter))?;
41 for hit in &report.hits {
42 println!(
43 " {:<10} distance={:.4} {}",
44 hit.id,
45 hit.distance,
46 hit.metadata.as_ref().unwrap()
47 );
48 }
49 println!(
50 "strategy={} visited={} selectivity={:.1}% time={:?}",
51 report.strategy,
52 report.visited,
53 report.filter_selectivity.unwrap_or(1.0) * 100.0,
54 report.elapsed
55 );
56
57 // Index health and recall against exact search.
58 let stats = docs.stats();
59 println!(
60 "layers={:?} unreachable={}",
61 stats.nodes_per_layer, stats.unreachable
62 );
63 let recall = docs.estimate_recall(&RecallOptions {
64 ef_values: vec![16, 64],
65 ..Default::default()
66 })?;
67 for point in &recall.points {
68 println!(
69 " ef={:<3} recall@{}={:.3}",
70 point.ef, recall.k, point.recall
71 );
72 }
73
74 db.save()?;
75 let reopened = Database::open(&path)?;
76 println!("reopened: {} records", reopened.collection("docs")?.len());
77 Ok(())
78}More examples
examples/bench.rs (line 71)
55fn main() -> recern_vector::Result<()> {
56 let args: Vec<String> = std::env::args().skip(1).collect();
57 let arg = |i: usize| {
58 args.get(i)
59 .map(|a| a.parse::<f64>().expect("numeric argument"))
60 };
61 let records = arg(0).map_or(50_000, |v| v as usize);
62 let dim = arg(1).map_or(128, |v| v as usize);
63 let noise = arg(2).map_or(1.0, |v| v as f32);
64 let path = std::env::temp_dir().join("recern-vector-bench.rvec");
65 let _ = std::fs::remove_file(&path);
66
67 let mut data = Clusters::new(100, dim, noise, 0x5EED);
68 let vectors: Vec<Vec<f32>> = (0..records).map(|_| data.sample()).collect();
69
70 let mut db = Database::create(&path)?;
71 let collection = db.create_collection("bench", CollectionConfig::new(dim, Metric::Cosine))?;
72 let start = Instant::now();
73 for (i, v) in vectors.iter().enumerate() {
74 collection.upsert(&i.to_string(), v, None)?;
75 }
76 let build = start.elapsed();
77
78 let start = Instant::now();
79 db.save()?;
80 let save = start.elapsed();
81 let start = Instant::now();
82 let db = Database::open(&path)?;
83 let open = start.elapsed();
84 let collection = db.collection("bench")?;
85 let stats = collection.stats();
86
87 println!("records {records} · dim {dim} · noise {noise} · cosine · m=16 ef_construction=200");
88 println!(
89 "build {:.2} s ({:.0} inserts/s)",
90 build.as_secs_f64(),
91 records as f64 / build.as_secs_f64()
92 );
93 println!(
94 "file {:.1} MB · save {:.0} ms · open {:.0} ms",
95 std::fs::metadata(&path)?.len() as f64 / 1_048_576.0,
96 save.as_secs_f64() * 1e3,
97 open.as_secs_f64() * 1e3
98 );
99 println!(
100 "graph layers {:?} · avg degree L0 {:.1} · unreachable {}",
101 stats.nodes_per_layer, stats.avg_degree_layer0, stats.unreachable
102 );
103
104 let report = collection.estimate_recall(&RecallOptions {
105 sample: 200,
106 k: 10,
107 ef_values: vec![10, 16, 32, 64, 128, 256],
108 seed: 7,
109 })?;
110 println!();
111 println!(
112 "exact scan p50 {:.3} ms",
113 report.exact_p50.as_secs_f64() * 1e3
114 );
115 println!(
116 "{:>6} {:>9} {:>10} {:>10} {:>9}",
117 "ef", "recall@10", "p50 ms", "p95 ms", "speedup"
118 );
119 for p in &report.points {
120 println!(
121 "{:>6} {:>9.3} {:>10.3} {:>10.3} {:>8.0}x",
122 p.ef,
123 p.recall,
124 p.p50.as_secs_f64() * 1e3,
125 p.p95.as_secs_f64() * 1e3,
126 report.exact_p50.as_secs_f64() / p.p50.as_secs_f64()
127 );
128 }
129
130 let _ = std::fs::remove_file(&path);
131 Ok(())
132}Sourcepub fn collection(&self, name: &str) -> Result<&Collection>
pub fn collection(&self, name: &str) -> Result<&Collection>
Examples found in repository?
examples/quickstart.rs (line 76)
9fn main() -> recern_vector::Result<()> {
10 let path = std::env::temp_dir().join("recern-vector-quickstart.rvec");
11 let _ = std::fs::remove_file(&path);
12 let _ = std::fs::remove_file(format!("{}.wal", path.display()));
13
14 let mut db = Database::create(&path)?;
15 let docs = db.create_collection("docs", CollectionConfig::new(32, Metric::Cosine))?;
16
17 // Batch insert: (id, vector, metadata). Built on all cores, atomic.
18 let records: Vec<_> = (0..10_000)
19 .map(|i| {
20 let vector: Vec<f32> = (0..32).map(|d| pseudo_random(i * 32 + d)).collect();
21 let lang = ["en", "de", "fr"][i % 3];
22 (
23 format!("doc-{i}"),
24 vector,
25 Some(json!({"lang": lang, "year": 2015 + i % 10})),
26 )
27 })
28 .collect();
29 let query = records[42].1.clone();
30 docs.upsert_many(records)?;
31 println!("{} records", docs.len());
32
33 // Search with a filter; explain() returns the hits and how they were found.
34 let filter = Filter::from_json(&json!({"lang": "en", "year": {"$gte": 2020}}))?;
35 // The same filter, built in code:
36 let _same = Filter::And(vec![
37 Filter::eq("lang", "en"),
38 Filter::between("year", Some(2020.0), None),
39 ]);
40 let report = docs.explain(&query, 3, &SearchOptions::default().filter(filter))?;
41 for hit in &report.hits {
42 println!(
43 " {:<10} distance={:.4} {}",
44 hit.id,
45 hit.distance,
46 hit.metadata.as_ref().unwrap()
47 );
48 }
49 println!(
50 "strategy={} visited={} selectivity={:.1}% time={:?}",
51 report.strategy,
52 report.visited,
53 report.filter_selectivity.unwrap_or(1.0) * 100.0,
54 report.elapsed
55 );
56
57 // Index health and recall against exact search.
58 let stats = docs.stats();
59 println!(
60 "layers={:?} unreachable={}",
61 stats.nodes_per_layer, stats.unreachable
62 );
63 let recall = docs.estimate_recall(&RecallOptions {
64 ef_values: vec![16, 64],
65 ..Default::default()
66 })?;
67 for point in &recall.points {
68 println!(
69 " ef={:<3} recall@{}={:.3}",
70 point.ef, recall.k, point.recall
71 );
72 }
73
74 db.save()?;
75 let reopened = Database::open(&path)?;
76 println!("reopened: {} records", reopened.collection("docs")?.len());
77 Ok(())
78}More examples
examples/bench.rs (line 84)
55fn main() -> recern_vector::Result<()> {
56 let args: Vec<String> = std::env::args().skip(1).collect();
57 let arg = |i: usize| {
58 args.get(i)
59 .map(|a| a.parse::<f64>().expect("numeric argument"))
60 };
61 let records = arg(0).map_or(50_000, |v| v as usize);
62 let dim = arg(1).map_or(128, |v| v as usize);
63 let noise = arg(2).map_or(1.0, |v| v as f32);
64 let path = std::env::temp_dir().join("recern-vector-bench.rvec");
65 let _ = std::fs::remove_file(&path);
66
67 let mut data = Clusters::new(100, dim, noise, 0x5EED);
68 let vectors: Vec<Vec<f32>> = (0..records).map(|_| data.sample()).collect();
69
70 let mut db = Database::create(&path)?;
71 let collection = db.create_collection("bench", CollectionConfig::new(dim, Metric::Cosine))?;
72 let start = Instant::now();
73 for (i, v) in vectors.iter().enumerate() {
74 collection.upsert(&i.to_string(), v, None)?;
75 }
76 let build = start.elapsed();
77
78 let start = Instant::now();
79 db.save()?;
80 let save = start.elapsed();
81 let start = Instant::now();
82 let db = Database::open(&path)?;
83 let open = start.elapsed();
84 let collection = db.collection("bench")?;
85 let stats = collection.stats();
86
87 println!("records {records} · dim {dim} · noise {noise} · cosine · m=16 ef_construction=200");
88 println!(
89 "build {:.2} s ({:.0} inserts/s)",
90 build.as_secs_f64(),
91 records as f64 / build.as_secs_f64()
92 );
93 println!(
94 "file {:.1} MB · save {:.0} ms · open {:.0} ms",
95 std::fs::metadata(&path)?.len() as f64 / 1_048_576.0,
96 save.as_secs_f64() * 1e3,
97 open.as_secs_f64() * 1e3
98 );
99 println!(
100 "graph layers {:?} · avg degree L0 {:.1} · unreachable {}",
101 stats.nodes_per_layer, stats.avg_degree_layer0, stats.unreachable
102 );
103
104 let report = collection.estimate_recall(&RecallOptions {
105 sample: 200,
106 k: 10,
107 ef_values: vec![10, 16, 32, 64, 128, 256],
108 seed: 7,
109 })?;
110 println!();
111 println!(
112 "exact scan p50 {:.3} ms",
113 report.exact_p50.as_secs_f64() * 1e3
114 );
115 println!(
116 "{:>6} {:>9} {:>10} {:>10} {:>9}",
117 "ef", "recall@10", "p50 ms", "p95 ms", "speedup"
118 );
119 for p in &report.points {
120 println!(
121 "{:>6} {:>9.3} {:>10.3} {:>10.3} {:>8.0}x",
122 p.ef,
123 p.recall,
124 p.p50.as_secs_f64() * 1e3,
125 p.p95.as_secs_f64() * 1e3,
126 report.exact_p50.as_secs_f64() / p.p50.as_secs_f64()
127 );
128 }
129
130 let _ = std::fs::remove_file(&path);
131 Ok(())
132}pub fn collection_mut(&mut self, name: &str) -> Result<&mut Collection>
pub fn drop_collection(&mut self, name: &str) -> Result<()>
Sourcepub fn collections(&self) -> impl Iterator<Item = &Collection>
pub fn collections(&self) -> impl Iterator<Item = &Collection>
Examples found in repository?
examples/search_bench.rs (line 26)
23fn main() {
24 let args: Vec<String> = std::env::args().skip(1).collect();
25 let db = Database::open(&args[0]).expect("index");
26 let c = db.collections().next().expect("a collection");
27 let dim = c.config().dim;
28 let queries = read(&args[1], f32::from_le_bytes);
29 let truth = read(&args[2], u32::from_le_bytes);
30 let efs: Vec<usize> = args
31 .get(3)
32 .map_or("10,20,40,80,160", String::as_str)
33 .split(',')
34 .map(|e| e.parse().unwrap())
35 .collect();
36 let n = queries.len() / dim;
37
38 println!(
39 "{:>5} {:>8} {:>9} {:>9} {:>10}",
40 "ef", "recall", "mean µs", "p50 µs", "distances"
41 );
42 for ef in efs {
43 let options = SearchOptions::default().ef(ef);
44 let (mut times, mut found, mut distances) = (Vec::with_capacity(n), 0, 0);
45 for (q, t) in queries
46 .chunks_exact(dim)
47 .zip(truth.as_chunks::<10>().0.iter())
48 {
49 let start = Instant::now();
50 let report = c.explain(q, 10, &options).unwrap();
51 times.push(start.elapsed().as_secs_f64() * 1e6);
52 distances += report.distance_computations;
53 found += report
54 .hits
55 .iter()
56 .filter(|h| t.contains(&h.id.parse::<u32>().unwrap()))
57 .count();
58 }
59 let mean = times.iter().sum::<f64>() / n as f64;
60 times.sort_by(f64::total_cmp);
61 println!(
62 "{ef:>5} {:>8.4} {mean:>9.1} {:>9.1} {:>10}",
63 found as f64 / (n * 10) as f64,
64 times[n / 2],
65 distances / n
66 );
67 }
68}Sourcepub fn save(&self) -> Result<()>
pub fn save(&self) -> Result<()>
Appends changed pages and fsyncs a commit. The base snapshot is unchanged except when migrating a format-1 database. No-op saves do not grow WAL.
Examples found in repository?
examples/quickstart.rs (line 74)
9fn main() -> recern_vector::Result<()> {
10 let path = std::env::temp_dir().join("recern-vector-quickstart.rvec");
11 let _ = std::fs::remove_file(&path);
12 let _ = std::fs::remove_file(format!("{}.wal", path.display()));
13
14 let mut db = Database::create(&path)?;
15 let docs = db.create_collection("docs", CollectionConfig::new(32, Metric::Cosine))?;
16
17 // Batch insert: (id, vector, metadata). Built on all cores, atomic.
18 let records: Vec<_> = (0..10_000)
19 .map(|i| {
20 let vector: Vec<f32> = (0..32).map(|d| pseudo_random(i * 32 + d)).collect();
21 let lang = ["en", "de", "fr"][i % 3];
22 (
23 format!("doc-{i}"),
24 vector,
25 Some(json!({"lang": lang, "year": 2015 + i % 10})),
26 )
27 })
28 .collect();
29 let query = records[42].1.clone();
30 docs.upsert_many(records)?;
31 println!("{} records", docs.len());
32
33 // Search with a filter; explain() returns the hits and how they were found.
34 let filter = Filter::from_json(&json!({"lang": "en", "year": {"$gte": 2020}}))?;
35 // The same filter, built in code:
36 let _same = Filter::And(vec![
37 Filter::eq("lang", "en"),
38 Filter::between("year", Some(2020.0), None),
39 ]);
40 let report = docs.explain(&query, 3, &SearchOptions::default().filter(filter))?;
41 for hit in &report.hits {
42 println!(
43 " {:<10} distance={:.4} {}",
44 hit.id,
45 hit.distance,
46 hit.metadata.as_ref().unwrap()
47 );
48 }
49 println!(
50 "strategy={} visited={} selectivity={:.1}% time={:?}",
51 report.strategy,
52 report.visited,
53 report.filter_selectivity.unwrap_or(1.0) * 100.0,
54 report.elapsed
55 );
56
57 // Index health and recall against exact search.
58 let stats = docs.stats();
59 println!(
60 "layers={:?} unreachable={}",
61 stats.nodes_per_layer, stats.unreachable
62 );
63 let recall = docs.estimate_recall(&RecallOptions {
64 ef_values: vec![16, 64],
65 ..Default::default()
66 })?;
67 for point in &recall.points {
68 println!(
69 " ef={:<3} recall@{}={:.3}",
70 point.ef, recall.k, point.recall
71 );
72 }
73
74 db.save()?;
75 let reopened = Database::open(&path)?;
76 println!("reopened: {} records", reopened.collection("docs")?.len());
77 Ok(())
78}More examples
examples/bench.rs (line 79)
55fn main() -> recern_vector::Result<()> {
56 let args: Vec<String> = std::env::args().skip(1).collect();
57 let arg = |i: usize| {
58 args.get(i)
59 .map(|a| a.parse::<f64>().expect("numeric argument"))
60 };
61 let records = arg(0).map_or(50_000, |v| v as usize);
62 let dim = arg(1).map_or(128, |v| v as usize);
63 let noise = arg(2).map_or(1.0, |v| v as f32);
64 let path = std::env::temp_dir().join("recern-vector-bench.rvec");
65 let _ = std::fs::remove_file(&path);
66
67 let mut data = Clusters::new(100, dim, noise, 0x5EED);
68 let vectors: Vec<Vec<f32>> = (0..records).map(|_| data.sample()).collect();
69
70 let mut db = Database::create(&path)?;
71 let collection = db.create_collection("bench", CollectionConfig::new(dim, Metric::Cosine))?;
72 let start = Instant::now();
73 for (i, v) in vectors.iter().enumerate() {
74 collection.upsert(&i.to_string(), v, None)?;
75 }
76 let build = start.elapsed();
77
78 let start = Instant::now();
79 db.save()?;
80 let save = start.elapsed();
81 let start = Instant::now();
82 let db = Database::open(&path)?;
83 let open = start.elapsed();
84 let collection = db.collection("bench")?;
85 let stats = collection.stats();
86
87 println!("records {records} · dim {dim} · noise {noise} · cosine · m=16 ef_construction=200");
88 println!(
89 "build {:.2} s ({:.0} inserts/s)",
90 build.as_secs_f64(),
91 records as f64 / build.as_secs_f64()
92 );
93 println!(
94 "file {:.1} MB · save {:.0} ms · open {:.0} ms",
95 std::fs::metadata(&path)?.len() as f64 / 1_048_576.0,
96 save.as_secs_f64() * 1e3,
97 open.as_secs_f64() * 1e3
98 );
99 println!(
100 "graph layers {:?} · avg degree L0 {:.1} · unreachable {}",
101 stats.nodes_per_layer, stats.avg_degree_layer0, stats.unreachable
102 );
103
104 let report = collection.estimate_recall(&RecallOptions {
105 sample: 200,
106 k: 10,
107 ef_values: vec![10, 16, 32, 64, 128, 256],
108 seed: 7,
109 })?;
110 println!();
111 println!(
112 "exact scan p50 {:.3} ms",
113 report.exact_p50.as_secs_f64() * 1e3
114 );
115 println!(
116 "{:>6} {:>9} {:>10} {:>10} {:>9}",
117 "ef", "recall@10", "p50 ms", "p95 ms", "speedup"
118 );
119 for p in &report.points {
120 println!(
121 "{:>6} {:>9.3} {:>10.3} {:>10.3} {:>8.0}x",
122 p.ef,
123 p.recall,
124 p.p50.as_secs_f64() * 1e3,
125 p.p95.as_secs_f64() * 1e3,
126 report.exact_p50.as_secs_f64() / p.p50.as_secs_f64()
127 );
128 }
129
130 let _ = std::fs::remove_file(&path);
131 Ok(())
132}Sourcepub fn checkpoint(&self) -> Result<()>
pub fn checkpoint(&self) -> Result<()>
Atomically merges committed and pending changes into the base snapshot, then resets WAL. A crash between those steps is safe to recover.
Auto Trait Implementations§
impl !Freeze for Database
impl RefUnwindSafe for Database
impl Send for Database
impl Sync for Database
impl Unpin for Database
impl UnsafeUnpin for Database
impl UnwindSafe for Database
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
Mutably borrows from an owned value. Read more