pub struct KvReader { /* private fields */ }Expand description
A reader over one seg file set (.kv data, optional .bt index, optional .kvei
existence filter).
Implementations§
Source§impl KvReader
impl KvReader
Sourcepub fn open(kv_path: impl AsRef<Path>) -> Result<KvReader>
pub fn open(kv_path: impl AsRef<Path>) -> Result<KvReader>
Open a .kv file and any sibling .bt / .kvei files found next to it (same
base name). The existence filter is loaded but not used for lookups until a salt
is supplied via enable_bloom.
Examples found in repository?
10fn main() -> Result<(), Box<dyn std::error::Error>> {
11 let mut args = std::env::args().skip(1);
12 let in_kv = args
13 .next()
14 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
15 let out_kv = args
16 .next()
17 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
18 let salt = args.next().and_then(salt_from_file);
19
20 // Read the source words (key/value pairs) and stream them into a DomainWriter.
21 let src = Seg::open(&in_kv)?;
22 let n_words = src.words_count();
23 println!("source: {} words ({} keys)", n_words, n_words / 2);
24
25 let t = Instant::now();
26 let mut w = DomainWriter::create(
27 &out_kv,
28 DomainOptions {
29 salt,
30 ..Default::default()
31 },
32 )?;
33 let mut g = src.getter();
34 while g.has_next() {
35 let key = g.next();
36 let value = if g.has_next() { g.next() } else { Vec::new() };
37 w.add(&key, &value)?;
38 }
39 let paths = w.finish()?;
40 println!("wrote {:?} in {:?}", paths, t.elapsed());
41
42 // Verify: every word matches the source byte-for-byte.
43 let dst = Seg::open(&out_kv)?;
44 assert_eq!(dst.words_count(), n_words);
45 let (mut a, mut b) = (src.getter(), dst.getter());
46 while a.has_next() {
47 assert_eq!(a.next(), b.next(), "word mismatch after re-encode");
48 }
49 assert!(!b.has_next());
50 println!("round-trip OK: all {n_words} words identical");
51
52 // If we built a bloom, confirm it accelerates lookups without false negatives.
53 if let Some(s) = salt {
54 let mut r = KvReader::open(&out_kv)?;
55 assert!(
56 r.enable_bloom(Salt::Known(s)),
57 "rebuilt .kvei failed to validate"
58 );
59 let mut checked = 0;
60 for kv in r.iter().step_by(997).take(500) {
61 let (k, v) = kv?;
62 assert_eq!(r.get(&k)?.as_deref(), Some(v.as_slice()));
63 checked += 1;
64 }
65 println!("bloom enabled; {checked} sampled lookups OK");
66 }
67
68 println!("\nOK");
69 Ok(())
70}More examples
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 open_with(
kv_path: impl AsRef<Path>,
opts: OpenOptions,
) -> Result<KvReader>
pub fn open_with( kv_path: impl AsRef<Path>, opts: OpenOptions, ) -> Result<KvReader>
Like open but with explicit seg OpenOptions (e.g. for
files carrying out-of-band metadata).
Sourcepub fn bloom_active(&self) -> bool
pub fn bloom_active(&self) -> bool
Whether the bloom filter is active for lookups — i.e. a .kvei is present and a
salt has been validated against real keys via enable_bloom.
Sourcepub fn seg(&self) -> &Seg
pub fn seg(&self) -> &Seg
The underlying seg data file.
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 index(&self) -> Option<&BtreeIndex>
pub fn index(&self) -> Option<&BtreeIndex>
The B-tree index, if a .bt was found.
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 existence_filter(&self) -> Option<&ExistenceFilter>
pub fn existence_filter(&self) -> Option<&ExistenceFilter>
The existence filter, if a .kvei was found.
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 salt(&self) -> Option<u32>
pub fn salt(&self) -> Option<u32>
The active bloom salt, if enable_bloom has succeeded.
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_count(&self) -> u64
pub fn key_count(&self) -> u64
Number of keys: from the .bt index if present, otherwise inferred as
words_count / 2 (domain files store alternating key/value words).
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 enable_bloom(&mut self, salt: Salt) -> bool
pub fn enable_bloom(&mut self, salt: Salt) -> bool
Enable the .kvei bloom as a negative-lookup accelerator, resolving the salt per
Salt. Returns true only if a usable bloom is present and the resolved salt
self-validates against real keys (so a wrong salt can never cause a missed key —
it just leaves lookups unaccelerated).
Examples found in repository?
10fn main() -> Result<(), Box<dyn std::error::Error>> {
11 let mut args = std::env::args().skip(1);
12 let in_kv = args
13 .next()
14 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
15 let out_kv = args
16 .next()
17 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
18 let salt = args.next().and_then(salt_from_file);
19
20 // Read the source words (key/value pairs) and stream them into a DomainWriter.
21 let src = Seg::open(&in_kv)?;
22 let n_words = src.words_count();
23 println!("source: {} words ({} keys)", n_words, n_words / 2);
24
25 let t = Instant::now();
26 let mut w = DomainWriter::create(
27 &out_kv,
28 DomainOptions {
29 salt,
30 ..Default::default()
31 },
32 )?;
33 let mut g = src.getter();
34 while g.has_next() {
35 let key = g.next();
36 let value = if g.has_next() { g.next() } else { Vec::new() };
37 w.add(&key, &value)?;
38 }
39 let paths = w.finish()?;
40 println!("wrote {:?} in {:?}", paths, t.elapsed());
41
42 // Verify: every word matches the source byte-for-byte.
43 let dst = Seg::open(&out_kv)?;
44 assert_eq!(dst.words_count(), n_words);
45 let (mut a, mut b) = (src.getter(), dst.getter());
46 while a.has_next() {
47 assert_eq!(a.next(), b.next(), "word mismatch after re-encode");
48 }
49 assert!(!b.has_next());
50 println!("round-trip OK: all {n_words} words identical");
51
52 // If we built a bloom, confirm it accelerates lookups without false negatives.
53 if let Some(s) = salt {
54 let mut r = KvReader::open(&out_kv)?;
55 assert!(
56 r.enable_bloom(Salt::Known(s)),
57 "rebuilt .kvei failed to validate"
58 );
59 let mut checked = 0;
60 for kv in r.iter().step_by(997).take(500) {
61 let (k, v) = kv?;
62 assert_eq!(r.get(&k)?.as_deref(), Some(v.as_slice()));
63 checked += 1;
64 }
65 println!("bloom enabled; {checked} sampled lookups OK");
66 }
67
68 println!("\nOK");
69 Ok(())
70}More examples
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 find_salt(&self, threads: usize) -> Option<u32>
pub fn find_salt(&self, threads: usize) -> Option<u32>
Brute-force the bloom salt by requiring a batch of real keys to all hit the
filter, using threads workers. Returns None if no .kvei bloom is usable or
no salt validates (e.g. a fuse-filter or format mismatch).
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 advise_random(&self) -> Result<()>
pub fn advise_random(&self) -> Result<()>
Advise the kernel that this file set is read by point lookup, so a page fault should read one page instead of a read-ahead window.
A binary search touches a handful of scattered pages, and the kernel’s default fault-around then reads far more than is used — on a file much larger than RAM that read amplification dominates lookup latency. This is not the default, because suppressing read-ahead is a regression for a file small enough to sit in the page cache, where the surplus pages get used by later lookups anyway. Set it when the data is large relative to RAM; leave it alone otherwise.
Advice is a hint: errors are reported but ignoring them is safe, and on platforms
without madvise this does nothing.
Sourcepub fn advise_sequential(&self) -> Result<()>
pub fn advise_sequential(&self) -> Result<()>
Advise the kernel that this .kv is about to be read front to back — before an
iter or a merge — so read-ahead works in your favour.
Sourcepub fn index_bytes(&self) -> u64
pub fn index_bytes(&self) -> u64
Bytes preload_index would make resident: the .bt,
plus the .kvei when the bloom is active. Use it to budget before calling.
Sourcepub fn preload_index(&self) -> u64
pub fn preload_index(&self) -> u64
Read the index files into the page cache and return once they are resident, reporting how many bytes were loaded.
A point lookup touches the .bt far more than the .kv — every search
comparison reads the Elias-Fano offset array, while only the final block of keys
is decompressed — and the .bt is one to two orders of magnitude smaller. On a
machine with RAM to spare, holding the whole index resident removes nearly all
the remaining faults: on a 37 GiB file set with a 1.4 GiB .bt, cold lookups
went from ~440 µs to ~155 µs, for a one-off ~0.7 s load.
The .kvei is included only when the bloom is active, since otherwise it is
never read. Loading is a hint to the kernel, not a reservation: these pages can
still be evicted under memory pressure — see lock_index
to prevent that.
Sourcepub fn lock_index(&self) -> Result<()>
pub fn lock_index(&self) -> Result<()>
Pin the index files in RAM with mlock, so the kernel cannot evict them.
Stronger than preload_index, and worth it when a
large .kv is streaming through the page cache and would otherwise push the
index back out. Same file selection: the .bt, plus the .kvei when the bloom
is active.
Fails with ENOMEM (or EPERM) if the total exceeds RLIMIT_MEMLOCK, which is
commonly a few megabytes by default; check index_bytes
against ulimit -l first. A failure is safe to ignore — it just leaves the pages
evictable — but note the limit applies per process across every locked mapping.
Unix only: elsewhere this reports ErrorKind::Unsupported rather than quietly
doing nothing, since the point of the call is a guarantee.
preload_index still works everywhere.
Preloads first: mlock faults the pages in itself, but page-at-a-time, so
warming them sequentially beforehand is markedly faster.
Sourcepub fn unlock_index(&self) -> Result<()>
pub fn unlock_index(&self) -> Result<()>
Release the pages pinned by lock_index.
Sourcepub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>>
pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>>
Look up key, returning its value if present.
Uses, in order: the bloom filter for a fast definite-absent answer (if enabled),
then the .bt index for an O(log n) binary search, or — if there is no index —
an ordered linear scan.
Examples found in repository?
10fn main() -> Result<(), Box<dyn std::error::Error>> {
11 let mut args = std::env::args().skip(1);
12 let in_kv = args
13 .next()
14 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
15 let out_kv = args
16 .next()
17 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
18 let salt = args.next().and_then(salt_from_file);
19
20 // Read the source words (key/value pairs) and stream them into a DomainWriter.
21 let src = Seg::open(&in_kv)?;
22 let n_words = src.words_count();
23 println!("source: {} words ({} keys)", n_words, n_words / 2);
24
25 let t = Instant::now();
26 let mut w = DomainWriter::create(
27 &out_kv,
28 DomainOptions {
29 salt,
30 ..Default::default()
31 },
32 )?;
33 let mut g = src.getter();
34 while g.has_next() {
35 let key = g.next();
36 let value = if g.has_next() { g.next() } else { Vec::new() };
37 w.add(&key, &value)?;
38 }
39 let paths = w.finish()?;
40 println!("wrote {:?} in {:?}", paths, t.elapsed());
41
42 // Verify: every word matches the source byte-for-byte.
43 let dst = Seg::open(&out_kv)?;
44 assert_eq!(dst.words_count(), n_words);
45 let (mut a, mut b) = (src.getter(), dst.getter());
46 while a.has_next() {
47 assert_eq!(a.next(), b.next(), "word mismatch after re-encode");
48 }
49 assert!(!b.has_next());
50 println!("round-trip OK: all {n_words} words identical");
51
52 // If we built a bloom, confirm it accelerates lookups without false negatives.
53 if let Some(s) = salt {
54 let mut r = KvReader::open(&out_kv)?;
55 assert!(
56 r.enable_bloom(Salt::Known(s)),
57 "rebuilt .kvei failed to validate"
58 );
59 let mut checked = 0;
60 for kv in r.iter().step_by(997).take(500) {
61 let (k, v) = kv?;
62 assert_eq!(r.get(&k)?.as_deref(), Some(v.as_slice()));
63 checked += 1;
64 }
65 println!("bloom enabled; {checked} sampled lookups OK");
66 }
67
68 println!("\nOK");
69 Ok(())
70}More examples
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 iter(&self) -> KvIter<'_> ⓘ
pub fn iter(&self) -> KvIter<'_> ⓘ
Iterate every (key, value) pair sequentially, in stored (key) order.
Examples found in repository?
10fn main() -> Result<(), Box<dyn std::error::Error>> {
11 let mut args = std::env::args().skip(1);
12 let in_kv = args
13 .next()
14 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
15 let out_kv = args
16 .next()
17 .expect("usage: recode <in.kv> <out.kv> [salt-state.txt]");
18 let salt = args.next().and_then(salt_from_file);
19
20 // Read the source words (key/value pairs) and stream them into a DomainWriter.
21 let src = Seg::open(&in_kv)?;
22 let n_words = src.words_count();
23 println!("source: {} words ({} keys)", n_words, n_words / 2);
24
25 let t = Instant::now();
26 let mut w = DomainWriter::create(
27 &out_kv,
28 DomainOptions {
29 salt,
30 ..Default::default()
31 },
32 )?;
33 let mut g = src.getter();
34 while g.has_next() {
35 let key = g.next();
36 let value = if g.has_next() { g.next() } else { Vec::new() };
37 w.add(&key, &value)?;
38 }
39 let paths = w.finish()?;
40 println!("wrote {:?} in {:?}", paths, t.elapsed());
41
42 // Verify: every word matches the source byte-for-byte.
43 let dst = Seg::open(&out_kv)?;
44 assert_eq!(dst.words_count(), n_words);
45 let (mut a, mut b) = (src.getter(), dst.getter());
46 while a.has_next() {
47 assert_eq!(a.next(), b.next(), "word mismatch after re-encode");
48 }
49 assert!(!b.has_next());
50 println!("round-trip OK: all {n_words} words identical");
51
52 // If we built a bloom, confirm it accelerates lookups without false negatives.
53 if let Some(s) = salt {
54 let mut r = KvReader::open(&out_kv)?;
55 assert!(
56 r.enable_bloom(Salt::Known(s)),
57 "rebuilt .kvei failed to validate"
58 );
59 let mut checked = 0;
60 for kv in r.iter().step_by(997).take(500) {
61 let (k, v) = kv?;
62 assert_eq!(r.get(&k)?.as_deref(), Some(v.as_slice()));
63 checked += 1;
64 }
65 println!("bloom enabled; {checked} sampled lookups OK");
66 }
67
68 println!("\nOK");
69 Ok(())
70}More examples
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}