format: group walks go on past a failed node and hint what they read next
Listing a large group over openUrl still took 6-11 passes (network round
trips) for the reviewer's 3000-dataset h5py file: each pass only found
the structures the walk reached before its first miss.
- The v1 and v2 B-tree collectors descend into every child of a node
after one fails (they only read the siblings before, so a sibling's
subtree came a pass later), then return the first error: results and
errors unchanged. The v2 walk stops once its record budget is spent,
so a shared-subtree tree still cannot multiply the work.
- Hints (`Storage::hint`, a no-op for every backend but the lazy one):
a group B-tree node's and a symbol table node's body (read once their
header gives a length, a round trip later when the body is in the
next block), an object header's first chunk and its continuation
chunks, the symbol table nodes a B-tree leaf names, a dense group's
name index header and the heap's root block (both read right after
the heap header). A listing also hints every child's object header as
its entry is read, even after a failure, and every direct block of a
dense group's heap (reading the indirect blocks, at most 4096 entries
and 4 levels deep); a lookup does not.
- The fractal heap's indirect-block layout (entry sizes, where the first
n entries end) is one helper used by the object reads and the hints.
Measured with tests/lazy.rs listing_cost_of_a_given_file on an h5py file
like the reviewer's (3000 datasets of 64 KiB, 198 MB), list('/'),
passes/requests/bytes, before -> after:
earliest, 1 MiB: 6/73/192.5 MB -> 4/68/192.5 MB
earliest, 64 KiB: 8/531/35.2 MB -> 5/530/35.3 MB
latest, 1 MiB: 9/98/196.5 MB -> 5/86/196.5 MB
latest, 64 KiB: 11/452/29.6 MB -> 6/454/30.5 MB
listing_a_large_group_takes_a_few_passes (512-byte blocks), budgets
tightened to the new counts: FileBuilder 600 children 5 -> 4 passes,
h5py 2000 children earliest 8 -> 5, latest 11 -> 6.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -194,10 +194,17 @@ const MAX_DEPTH: u16 = 64;
|
||||
|
||||
/// Take `n` records from the traversal's budget, or refuse the tree.
|
||||
fn spend(budget: &mut usize, n: usize) -> Result<(), FormatError> {
|
||||
*budget = budget
|
||||
.checked_sub(n)
|
||||
.ok_or(FormatError::NestingDepthExceeded)?;
|
||||
Ok(())
|
||||
match budget.checked_sub(n) {
|
||||
Some(left) => {
|
||||
*budget = left;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
// Spent: a walk that goes on after a failure stops here.
|
||||
*budget = 0;
|
||||
Err(FormatError::NestingDepthExceeded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all records from a B-tree v2 by traversing from the root.
|
||||
@@ -504,16 +511,18 @@ fn collect_internal_records<S: Storage + ?Sized>(
|
||||
|
||||
// Interleave: child[0], record[0], child[1], record[1], ..., child[nr]
|
||||
// We collect child[0] records, then record[0], then child[1], etc.
|
||||
// After the first child that fails, the others are only touched (see
|
||||
// `storage::touch`); that error is returned.
|
||||
// A child that fails does not stop the walk: the others are still
|
||||
// descended into (their records are dropped with the result), then the
|
||||
// first error is returned, as when stopping there. A storage that
|
||||
// records what it lacks (see `storage::touch`) so learns every node the
|
||||
// walk can reach in one attempt. The record budget is spent as before,
|
||||
// so the walk is no longer than a successful one.
|
||||
let mut failed = None;
|
||||
for (i, &(child_addr, child_nrec)) in node.children.iter().enumerate() {
|
||||
if failed.is_some() {
|
||||
let len = usize::try_from(node_size)
|
||||
.unwrap_or(usize::MAX)
|
||||
.min(1 << 16);
|
||||
crate::storage::touch(file, child_addr, len);
|
||||
continue;
|
||||
if failed.is_some() && *budget == 0 {
|
||||
// The record budget is spent: the tree is refused, and a walk
|
||||
// over what is left could be as long as the one it bounds.
|
||||
break;
|
||||
}
|
||||
if let Err(e) = (|| -> Result<(), FormatError> {
|
||||
if child_depth == 0 {
|
||||
@@ -553,7 +562,7 @@ fn collect_internal_records<S: Storage + ?Sized>(
|
||||
}
|
||||
Ok(())
|
||||
})() {
|
||||
failed = Some(e);
|
||||
failed.get_or_insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user