pub struct BtreeIndex { /* private fields */ }Expand description
A .bt index: the Elias-Fano offset array plus, when known, the B-tree fanout M
and the di-node array used to narrow lookups.
Implementations§
Source§impl BtreeIndex
impl BtreeIndex
Sourcepub fn open(path: impl AsRef<Path>) -> Result<BtreeIndex>
pub fn open(path: impl AsRef<Path>) -> Result<BtreeIndex>
Open and parse a .bt file, auto-detecting the legacy vs footer layout.
Sourcepub fn key_count(&self) -> u64
pub fn key_count(&self) -> u64
Number of indexed keys.
Examples found in repository?
15fn main() -> Result<(), Box<dyn std::error::Error>> {
16 let mut args = std::env::args().skip(1);
17 let kv_path = args
18 .next()
19 .expect("usage: inspect <path-to.kv> [salt-state.txt]");
20 let salt_path = args.next();
21
22 let t = Instant::now();
23 let mut r = KvReader::open(&kv_path)?;
24 println!("opened {kv_path} in {:?}", t.elapsed());
25 println!(" seg version : v{}", r.seg().version());
26 println!(" words_count : {}", r.seg().words_count());
27 println!(" empty_words : {}", r.seg().empty_words_count());
28 println!(" key_count : {}", r.key_count());
29 println!(" has .bt index : {}", r.index().is_some());
30 if let Some(idx) = r.index() {
31 println!(" bt key_count : {}", idx.key_count());
32 println!(" bt M : {:?}", idx.m());
33 }
34 match r.existence_filter() {
35 Some(f) => println!(
36 " .kvei kind : {:?} (accelerating={})",
37 f.kind(),
38 f.is_accelerating()
39 ),
40 None => println!(" .kvei : (none)"),
41 }
42
43 // First few (key, value) pairs.
44 println!("\nfirst pairs:");
45 let mut sample: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
46 for (i, kv) in r.iter().enumerate().take(5) {
47 let (k, v) = kv?;
48 println!(
49 " [{i}] key={} ({} B) value={} B",
50 hex(&k),
51 k.len(),
52 v.len()
53 );
54 sample.push((k, v));
55 }
56
57 // Round-trip a spread of real keys through get().
58 println!("\nround-trip lookups (spread across the file):");
59 let n = r.key_count();
60 let mut checked = 0u64;
61 let mut probe_keys: Vec<Vec<u8>> = Vec::new();
62 if let Some(idx) = r.index() {
63 let g_count = 12u64.min(n.max(1));
64 let mut g = r.seg().getter();
65 for s in 0..g_count {
66 let di = s * n / g_count;
67 if let Some(off) = idx.key_offset(di) {
68 g.reset(off);
69 probe_keys.push(g.next());
70 }
71 }
72 } else {
73 probe_keys = sample.iter().map(|(k, _)| k.clone()).collect();
74 }
75 let t = Instant::now();
76 for k in &probe_keys {
77 let got = r.get(k)?;
78 assert!(got.is_some(), "real key {} not found by get()", hex(k));
79 checked += 1;
80 }
81 let dt = t.elapsed();
82 println!(
83 " {checked} keys all found; avg {:?}/lookup",
84 dt.checked_div(checked.max(1) as u32).unwrap_or_default()
85 );
86
87 // Cross-check: get() value equals the value that follows the key in iteration order.
88 for (k, v) in &sample {
89 assert_eq!(
90 r.get(k)?.as_deref(),
91 Some(v.as_slice()),
92 "value mismatch for {}",
93 hex(k)
94 );
95 }
96 println!(" values match sequential iteration ✓");
97
98 // Negative lookup: a key we are confident is absent.
99 let absent = b"\xff_erigon_seg_definitely_absent_key_\xff";
100 println!(
101 "\nnegative lookup for a synthetic key: {:?}",
102 r.get(absent)?.map(|v| v.len())
103 );
104
105 // Salt resolution + bloom acceleration.
106 if r.existence_filter()
107 .map(|f| f.is_accelerating())
108 .unwrap_or(false)
109 {
110 // (a) brute-force find.
111 let t = Instant::now();
112 let found = r.find_salt(num_cpus());
113 println!(
114 "\nfind_salt -> {:?} (in {:?})",
115 found.map(|s| format!("{s:#010x}")),
116 t.elapsed()
117 );
118
119 // (b) known salt from the salt file, if provided.
120 let salt = match &salt_path {
121 Some(p) => salt_from_file(p),
122 None => None,
123 };
124 if let Some(s) = salt {
125 println!("salt-state.txt -> {s:#010x}");
126 if let Some(f) = found {
127 assert_eq!(f, s, "brute-forced salt disagrees with salt file");
128 }
129 }
130 let chosen = salt.map(Salt::Known).unwrap_or(Salt::Find(num_cpus()));
131 let enabled = r.enable_bloom(chosen);
132 println!(
133 "enable_bloom -> {enabled} (active salt = {:?})",
134 r.salt().map(|s| format!("{s:#010x}"))
135 );
136
137 if let (Some(s), Some(f)) = (r.salt(), r.existence_filter()) {
138 // Every real probe key must be reported present by the bloom.
139 let all_present = probe_keys
140 .iter()
141 .all(|k| f.contains_hash(murmur3_x64_128_h1(k, s)));
142 println!(
143 "bloom: all {} real probe keys reported present = {all_present}",
144 probe_keys.len()
145 );
146 assert!(
147 all_present,
148 "bloom false-negative on a real key (wrong salt?)"
149 );
150
151 // Timed lookups with bloom enabled (negatives short-circuit).
152 let t = Instant::now();
153 for k in &probe_keys {
154 let _ = r.get(k)?;
155 }
156 println!(
157 " {} bloom-gated lookups in {:?}",
158 probe_keys.len(),
159 t.elapsed()
160 );
161 }
162 }
163
164 println!("\nOK");
165 Ok(())
166}Sourcepub fn key_offset(&self, i: u64) -> Option<u64>
pub fn key_offset(&self, i: u64) -> Option<u64>
The .kv byte offset of the i-th key (0-based). Returns None if out of range.
Examples found in repository?
15fn main() -> Result<(), Box<dyn std::error::Error>> {
16 let mut args = std::env::args().skip(1);
17 let kv_path = args
18 .next()
19 .expect("usage: inspect <path-to.kv> [salt-state.txt]");
20 let salt_path = args.next();
21
22 let t = Instant::now();
23 let mut r = KvReader::open(&kv_path)?;
24 println!("opened {kv_path} in {:?}", t.elapsed());
25 println!(" seg version : v{}", r.seg().version());
26 println!(" words_count : {}", r.seg().words_count());
27 println!(" empty_words : {}", r.seg().empty_words_count());
28 println!(" key_count : {}", r.key_count());
29 println!(" has .bt index : {}", r.index().is_some());
30 if let Some(idx) = r.index() {
31 println!(" bt key_count : {}", idx.key_count());
32 println!(" bt M : {:?}", idx.m());
33 }
34 match r.existence_filter() {
35 Some(f) => println!(
36 " .kvei kind : {:?} (accelerating={})",
37 f.kind(),
38 f.is_accelerating()
39 ),
40 None => println!(" .kvei : (none)"),
41 }
42
43 // First few (key, value) pairs.
44 println!("\nfirst pairs:");
45 let mut sample: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
46 for (i, kv) in r.iter().enumerate().take(5) {
47 let (k, v) = kv?;
48 println!(
49 " [{i}] key={} ({} B) value={} B",
50 hex(&k),
51 k.len(),
52 v.len()
53 );
54 sample.push((k, v));
55 }
56
57 // Round-trip a spread of real keys through get().
58 println!("\nround-trip lookups (spread across the file):");
59 let n = r.key_count();
60 let mut checked = 0u64;
61 let mut probe_keys: Vec<Vec<u8>> = Vec::new();
62 if let Some(idx) = r.index() {
63 let g_count = 12u64.min(n.max(1));
64 let mut g = r.seg().getter();
65 for s in 0..g_count {
66 let di = s * n / g_count;
67 if let Some(off) = idx.key_offset(di) {
68 g.reset(off);
69 probe_keys.push(g.next());
70 }
71 }
72 } else {
73 probe_keys = sample.iter().map(|(k, _)| k.clone()).collect();
74 }
75 let t = Instant::now();
76 for k in &probe_keys {
77 let got = r.get(k)?;
78 assert!(got.is_some(), "real key {} not found by get()", hex(k));
79 checked += 1;
80 }
81 let dt = t.elapsed();
82 println!(
83 " {checked} keys all found; avg {:?}/lookup",
84 dt.checked_div(checked.max(1) as u32).unwrap_or_default()
85 );
86
87 // Cross-check: get() value equals the value that follows the key in iteration order.
88 for (k, v) in &sample {
89 assert_eq!(
90 r.get(k)?.as_deref(),
91 Some(v.as_slice()),
92 "value mismatch for {}",
93 hex(k)
94 );
95 }
96 println!(" values match sequential iteration ✓");
97
98 // Negative lookup: a key we are confident is absent.
99 let absent = b"\xff_erigon_seg_definitely_absent_key_\xff";
100 println!(
101 "\nnegative lookup for a synthetic key: {:?}",
102 r.get(absent)?.map(|v| v.len())
103 );
104
105 // Salt resolution + bloom acceleration.
106 if r.existence_filter()
107 .map(|f| f.is_accelerating())
108 .unwrap_or(false)
109 {
110 // (a) brute-force find.
111 let t = Instant::now();
112 let found = r.find_salt(num_cpus());
113 println!(
114 "\nfind_salt -> {:?} (in {:?})",
115 found.map(|s| format!("{s:#010x}")),
116 t.elapsed()
117 );
118
119 // (b) known salt from the salt file, if provided.
120 let salt = match &salt_path {
121 Some(p) => salt_from_file(p),
122 None => None,
123 };
124 if let Some(s) = salt {
125 println!("salt-state.txt -> {s:#010x}");
126 if let Some(f) = found {
127 assert_eq!(f, s, "brute-forced salt disagrees with salt file");
128 }
129 }
130 let chosen = salt.map(Salt::Known).unwrap_or(Salt::Find(num_cpus()));
131 let enabled = r.enable_bloom(chosen);
132 println!(
133 "enable_bloom -> {enabled} (active salt = {:?})",
134 r.salt().map(|s| format!("{s:#010x}"))
135 );
136
137 if let (Some(s), Some(f)) = (r.salt(), r.existence_filter()) {
138 // Every real probe key must be reported present by the bloom.
139 let all_present = probe_keys
140 .iter()
141 .all(|k| f.contains_hash(murmur3_x64_128_h1(k, s)));
142 println!(
143 "bloom: all {} real probe keys reported present = {all_present}",
144 probe_keys.len()
145 );
146 assert!(
147 all_present,
148 "bloom false-negative on a real key (wrong salt?)"
149 );
150
151 // Timed lookups with bloom enabled (negatives short-circuit).
152 let t = Instant::now();
153 for k in &probe_keys {
154 let _ = r.get(k)?;
155 }
156 println!(
157 " {} bloom-gated lookups in {:?}",
158 probe_keys.len(),
159 t.elapsed()
160 );
161 }
162 }
163
164 println!("\nOK");
165 Ok(())
166}Sourcepub fn m(&self) -> Option<u64>
pub fn m(&self) -> Option<u64>
The B-tree fanout M, if the layout records it (footer layout only).
Examples found in repository?
15fn main() -> Result<(), Box<dyn std::error::Error>> {
16 let mut args = std::env::args().skip(1);
17 let kv_path = args
18 .next()
19 .expect("usage: inspect <path-to.kv> [salt-state.txt]");
20 let salt_path = args.next();
21
22 let t = Instant::now();
23 let mut r = KvReader::open(&kv_path)?;
24 println!("opened {kv_path} in {:?}", t.elapsed());
25 println!(" seg version : v{}", r.seg().version());
26 println!(" words_count : {}", r.seg().words_count());
27 println!(" empty_words : {}", r.seg().empty_words_count());
28 println!(" key_count : {}", r.key_count());
29 println!(" has .bt index : {}", r.index().is_some());
30 if let Some(idx) = r.index() {
31 println!(" bt key_count : {}", idx.key_count());
32 println!(" bt M : {:?}", idx.m());
33 }
34 match r.existence_filter() {
35 Some(f) => println!(
36 " .kvei kind : {:?} (accelerating={})",
37 f.kind(),
38 f.is_accelerating()
39 ),
40 None => println!(" .kvei : (none)"),
41 }
42
43 // First few (key, value) pairs.
44 println!("\nfirst pairs:");
45 let mut sample: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
46 for (i, kv) in r.iter().enumerate().take(5) {
47 let (k, v) = kv?;
48 println!(
49 " [{i}] key={} ({} B) value={} B",
50 hex(&k),
51 k.len(),
52 v.len()
53 );
54 sample.push((k, v));
55 }
56
57 // Round-trip a spread of real keys through get().
58 println!("\nround-trip lookups (spread across the file):");
59 let n = r.key_count();
60 let mut checked = 0u64;
61 let mut probe_keys: Vec<Vec<u8>> = Vec::new();
62 if let Some(idx) = r.index() {
63 let g_count = 12u64.min(n.max(1));
64 let mut g = r.seg().getter();
65 for s in 0..g_count {
66 let di = s * n / g_count;
67 if let Some(off) = idx.key_offset(di) {
68 g.reset(off);
69 probe_keys.push(g.next());
70 }
71 }
72 } else {
73 probe_keys = sample.iter().map(|(k, _)| k.clone()).collect();
74 }
75 let t = Instant::now();
76 for k in &probe_keys {
77 let got = r.get(k)?;
78 assert!(got.is_some(), "real key {} not found by get()", hex(k));
79 checked += 1;
80 }
81 let dt = t.elapsed();
82 println!(
83 " {checked} keys all found; avg {:?}/lookup",
84 dt.checked_div(checked.max(1) as u32).unwrap_or_default()
85 );
86
87 // Cross-check: get() value equals the value that follows the key in iteration order.
88 for (k, v) in &sample {
89 assert_eq!(
90 r.get(k)?.as_deref(),
91 Some(v.as_slice()),
92 "value mismatch for {}",
93 hex(k)
94 );
95 }
96 println!(" values match sequential iteration ✓");
97
98 // Negative lookup: a key we are confident is absent.
99 let absent = b"\xff_erigon_seg_definitely_absent_key_\xff";
100 println!(
101 "\nnegative lookup for a synthetic key: {:?}",
102 r.get(absent)?.map(|v| v.len())
103 );
104
105 // Salt resolution + bloom acceleration.
106 if r.existence_filter()
107 .map(|f| f.is_accelerating())
108 .unwrap_or(false)
109 {
110 // (a) brute-force find.
111 let t = Instant::now();
112 let found = r.find_salt(num_cpus());
113 println!(
114 "\nfind_salt -> {:?} (in {:?})",
115 found.map(|s| format!("{s:#010x}")),
116 t.elapsed()
117 );
118
119 // (b) known salt from the salt file, if provided.
120 let salt = match &salt_path {
121 Some(p) => salt_from_file(p),
122 None => None,
123 };
124 if let Some(s) = salt {
125 println!("salt-state.txt -> {s:#010x}");
126 if let Some(f) = found {
127 assert_eq!(f, s, "brute-forced salt disagrees with salt file");
128 }
129 }
130 let chosen = salt.map(Salt::Known).unwrap_or(Salt::Find(num_cpus()));
131 let enabled = r.enable_bloom(chosen);
132 println!(
133 "enable_bloom -> {enabled} (active salt = {:?})",
134 r.salt().map(|s| format!("{s:#010x}"))
135 );
136
137 if let (Some(s), Some(f)) = (r.salt(), r.existence_filter()) {
138 // Every real probe key must be reported present by the bloom.
139 let all_present = probe_keys
140 .iter()
141 .all(|k| f.contains_hash(murmur3_x64_128_h1(k, s)));
142 println!(
143 "bloom: all {} real probe keys reported present = {all_present}",
144 probe_keys.len()
145 );
146 assert!(
147 all_present,
148 "bloom false-negative on a real key (wrong salt?)"
149 );
150
151 // Timed lookups with bloom enabled (negatives short-circuit).
152 let t = Instant::now();
153 for k in &probe_keys {
154 let _ = r.get(k)?;
155 }
156 println!(
157 " {} bloom-gated lookups in {:?}",
158 probe_keys.len(),
159 t.elapsed()
160 );
161 }
162 }
163
164 println!("\nOK");
165 Ok(())
166}Sourcepub fn nodes(&self) -> Option<&Nodes>
pub fn nodes(&self) -> Option<&Nodes>
The di-node array, parsed on first call and cached thereafter.
Returns None for the legacy layout, for an empty index, or if the section does
not parse — in each case lookups simply fall back to the full binary search.
Parsing walks the whole node section once (ceil(key_count / M) entries) and
copies the keys into an arena, so the first call costs one pass over that
section and holds it in memory. It is deliberately not done at open time, so
opening a file only to scan it — merging, re-encoding — pays nothing.
Sourcepub fn narrow(&self, key: &[u8]) -> (u64, u64)
pub fn narrow(&self, key: &[u8]) -> (u64, u64)
Narrow a lookup for key to the half-open key-index range that can contain it,
using the di-nodes. Falls back to the full range when narrowing is unavailable.
Sourcepub fn advise_random(&self) -> Result<()>
pub fn advise_random(&self) -> Result<()>
Advise the kernel that this .bt is read in random order (point lookups). See
KvReader::advise_random.
Sourcepub fn mapped_bytes(&self) -> u64
pub fn mapped_bytes(&self) -> u64
Sourcepub fn preload(&self) -> u64
pub fn preload(&self) -> u64
Read the whole .bt into the page cache, returning once it is resident. See
KvReader::preload_index.
Sourcepub fn lock(&self) -> Result<()>
pub fn lock(&self) -> Result<()>
Pin the whole .bt in RAM with mlock. See
KvReader::lock_index for the caveats.
Sourcepub fn elias_fano(&self) -> Option<&EliasFano>
pub fn elias_fano(&self) -> Option<&EliasFano>
Borrow the underlying Elias-Fano offset array, if the index is non-empty.