Skip to content

Optimize split recovery metadata reads from split bundles - #6723

Merged
Mallets merged 9 commits into
mainfrom
mallets/fetch-file-from-split
Aug 31, 2026
Merged

Optimize split recovery metadata reads from split bundles#6723
Mallets merged 9 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

Comment thread quickwit/quickwit-storage/src/bundle_storage.rs Outdated
Comment thread quickwit/quickwit-storage/src/bundle_storage.rs Outdated
Comment thread quickwit/quickwit-storage/src/bundle_storage.rs Outdated
Comment thread quickwit/quickwit-storage/src/bundle_storage.rs Outdated

// Start with the requested window, but always cover the fixed trailer and never read before
// the beginning of the split.
let initial_tail_num_bytes = initial_tail_window_num_bytes

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.

nitpick. I find it always max/min have some conterintuitive aspect.

We think: the MAXIMUM accepted value is split_len, so we counterintuitively need to compute
x.min(split_len).

initial_tail_window_num_bytes.clamp() helps with that.

Granted... this goes against my common rant about the proliferation of needless methods in rust's std.

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.

I wasn't really aware of clamp(). Indeed it helps here. Done in 575229a.

Ok(footer_start..split_len)
}

fn locate_split_footer_range_in_tail(

@fulmicoton fulmicoton Aug 28, 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.

why is this so complicated? Is it explained somewhere?
Has there been a change of format?

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.

Yes, the format has been changed in #6689 and it is explained in:

/// `BundleDirectory` is a read-only directory that opens a "split bundle" and serves its files

Note that this PR does not modify the format.

storage: &dyn Storage,
split_path: &Path,
split_len: u64,
initial_tail_window_num_bytes: u64,

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 initial_tail_window_num_bytes useful as an argument? I only see a const being used?

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.

In the context of fetch_split_tail standalone function, I believe it's good to accept it as a parameter and it's the caller's responsibility to decide how big the initial_tail_window should be. The caller now uses a const, but any change in the logic of the caller shouldn't result in a change in the fetch_split_tail function itself.

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.

This is not a valid point, is it?
The function is private. The value will stay const forever and I don't see any reason to make it the caller responsibility to pick the value, apart from increasing complexity for no reason at all:
that function contract is needlessly complicated as a result.

/// The split length is provided by the caller (e.g. from object listing metadata) to avoid a
/// separate metadata request.
pub async fn fetch_file_from_split(
storage: Arc<dyn Storage>,

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.

should it be &dyn?

/// separate metadata request.
pub async fn fetch_file_from_split(
storage: Arc<dyn Storage>,
bundle_filepath: PathBuf,

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
bundle_filepath: PathBuf,
bundle_filepath: &Path,

/// separate metadata request.
pub async fn fetch_file_from_split(
storage: Arc<dyn Storage>,
bundle_filepath: PathBuf,

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.

bundle filepath? So the manifest is just another file of the bundle I presume.

But the implementation is very specifically targeting that we append at the very end right?
Shouldn't we specialize it to only apply to manifest files? (i don't know how you called them)

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.

Ah I guess this is not necessary. Worst case scenario we get it into fetch. Ok fair enough.

/// Use this only when retrieving a single file from the split. To retrieve multiple files,
/// prefer [`Self::open_from_storage`].
///
/// The split length is provided by the caller (e.g. from object listing metadata) to avoid a

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.

can you document the result semantics because this is not clear at all.

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 579c0b4

@Mallets
Mallets enabled auto-merge (squash) August 31, 2026 07:38
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T08:02:09.103511Z a12f394 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a12f3941c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quickwit/quickwit-storage/src/bundle_storage.rs
@Mallets
Mallets merged commit 2bccaf2 into main Aug 31, 2026
10 checks passed
@Mallets
Mallets deleted the mallets/fetch-file-from-split branch August 31, 2026 08:08
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