Skip to content

Optimize split recovery metadata reads from split bundles - #6723

Open
Mallets wants to merge 4 commits into
mainfrom
mallets/fetch-file-from-split
Open

Optimize split recovery metadata reads from split bundles#6723
Mallets wants to merge 4 commits into
mainfrom
mallets/fetch-file-from-split

Conversation

@Mallets

@Mallets Mallets commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Disaster recovery only needs the recovery metadata stored in each split, but it may need to inspect a large number of splits. Separate requests to discover each split’s size, locate its footer, and fetch the recovery metadata would multiply object-storage GETs across the bucket.

Bucket listings already provide each split’s object length, so callers can pass it directly and avoid a separate metadata request. We then optimistically read the final 1 MiB of the split. In the expected case, this contains the complete footer and recovery metadata, allowing retrieval with a single GET per split.

Details

  • Add BundleStorage::fetch_file_from_split for retrieving one bundled file when the split length is known.
  • Parse the footer from the initial tail read and reuse those bytes when they contain the requested file.
  • Widen the tail read or fetch the file separately only when an unusually large footer or legacy layout requires it.

Test plan

  • make fmt
  • cargo clippy -p quickwit-storage --all-features --tests
  • cargo nextest run -p quickwit-storage --all-features bundle_storage

Reuse tail bytes when possible to avoid opening the full bundle and reduce object storage requests.
@Mallets
Mallets marked this pull request as ready for review August 26, 2026 08:43
@Mallets
Mallets requested a review from a team as a code owner August 26, 2026 08:43
@Mallets Mallets changed the title Optimize single-file reads from split bundles Optimize recovery metadata reads from split bundles Aug 26, 2026
@Mallets Mallets changed the title Optimize recovery metadata reads from split bundles Optimize split recovery metadata reads from split bundles Aug 26, 2026
@dayaffe

dayaffe commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

/ci-run-all-tests

// If the initial tail also contains the file, reuse it and complete in one GET (at least).
// Otherwise, fetch the file with an additional GET.
let file_bytes = if file_range.start >= tail_start {
let relative_start = usize::try_from(file_range.start - tail_start)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when does this fail?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically it will never fail there, it will fail first in the fetch_split_tail.

The failure case is when running on 32 bits and the split is larger than 4GB. However, I'm not sure either that the whole system can run on 32 bits, in that case these faillable conversions can be replaced with a simple cast.

@fulmicoton-dd fulmicoton-dd Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI generates crazy defensive code like this.
Last week Cong had a PR with u64::try_from(usize),
and codex keep generating thing like this for me too.

I don't think we need to accept those. It hurts readability IMHO

@Mallets Mallets Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, u64::try_from(usize) is non-sense. But for faillable conversions on supported platforms it makes sense to me. Said that, we can consider 32 bits as a non-supported target.

Done in 5947c79.

Comment on lines +311 to +314
ensure!(
initial_tail_window_num_bytes > 0,
"split tail window must be positive"
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this important?

@Mallets Mallets Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not critical. Removed in 904fcfb.

.max(SPLIT_FOOTER_TRAILER_NUM_BYTES as u64)
.min(split_len);
// Read the tail in a loop until we find the footer.
let (tail_bytes, footer_range) = loop {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if possible avoid

  • loop without clear exit condition. It makes proofreading hard and is error prone.
  • break with value

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here... I don't understand why this is a loop?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 904fcfb.

loop is a way to retry the exact operation twice. Fine to make it explicit here.


fn locate_split_footer_range_in_tail(
split_len: u64,
tail_bytes: &OwnedBytes,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tail_bytes: &OwnedBytes,
tail_bytes: &[u8],

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants